Featured image of post iOS - SideTables

iOS - SideTables

SideTables 完全详解

SideTables 是 Objective-C 运行时全局核心枢纽,本质是 StripedMap<SideTable> 类型的单例对象,承担所有对象的强引用计数管理弱引用表管理线程安全锁三大核心职责,是 retain/release(强引用)、weak(弱引用)机制的底层基石。

1
2
3
4
static StripedMap<SideTable>& SideTables() {
    static StripedMap<SideTable> sSideTables;
    return sSideTables;
}

一、SideTables 核心定位与本质

特性 说明
类型 static StripedMap<SideTable>& SideTables()(全局单例,进程内唯一)
核心作用 为所有对象分配专属的 SideTable,通过「分段哈希 + 分片锁」保证强 / 弱引用操作的线程安全与性能
设计初衷 解决全局锁竞争问题:将全局操作拆分为多个分段(Stripe),每个分段独立加锁,提升并发性能

补充:SideTables 不是单一的 SideTable,而是分段哈希表容器StripedMap),容器内包含多个独立的 SideTable(默认 8 个,iOS 部分版本为 16 个),每个 SideTable 对应一个「分段锁」和一组对象的计数 / 弱引用数据。

二、先吃透 SideTable(SideTables 的核心元素)

SideTables 的核心是数组形式存储的 SideTable 实例,每个 SideTable 是一个独立的「功能单元」,结构如下(简化版底层源码):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct SideTable {
    // 1. 自旋锁:保证当前 SideTable 内所有操作的线程安全(分片锁)
    spinlock_t slock;         

    // 2. 强引用计数哈希表:存储对象的「补充引用计数」
    // 类型:DenseMap<void*, size_t>(key=对象地址,value=补充引用计数值)
    RefcountMap refcnts;       

    // 3. 弱引用表:存储对象的所有弱引用指针地址
    weak_table_t weak_table;   

    // 辅助:哈希表桶大小(优化性能)
    static size_t const TABLE_SIZE = 4096;
};

各组件详解

组件 数据类型 / 核心字段 核心作用
slock 自旋锁(spinlock_t 短时间忙等锁,适合 retain/release 这类短操作;保证当前 SideTable 内操作原子性
refcnts 哈希表(DenseMap<void*, size_t> 存储对象「溢出的强引用计数」(isa 内存不下时);key = 对象地址,value = 补充计数值
weak_table 包含 weak_entry_t* buckets 等字段 存储对象的弱引用指针列表:key = 对象地址,value = 该对象的所有 weak 指针地址数组

三、SideTables 的核心工作机制

1. 对象 → SideTable 的映射规则(核心)

每个对象的地址通过哈希算法,永久映射到 SideTables 中的一个固定 SideTable,规则如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// SideTables 内置的哈希函数(简化版)
size_t indexForPointer(const void *p) {
    // 1. 取对象地址的 uintptr_t 类型值(指针转整数)
    uintptr_t addr = reinterpret_cast<uintptr_t>(p);
    // 2. 右移4位:避开低位噪声(内存对齐导致低位固定)
    // 3. 按位与 (stripeCount-1):快速取模(stripeCount=8 → 0~7 索引)
    return (addr >> 4) & (stripeCount - 1);
}

// 获取对象对应的 SideTable
SideTable& table = SideTables()[obj];
  • 核心特性:同一对象的地址不变,永远映射到同一个 SideTable,保证计数 / 弱引用数据的一致性;
  • 性能优化:位运算替代取模(& 比 % 快 10 倍以上),哈希均匀性高,减少分段冲突。

2. 线程安全保障(分片锁机制)

锁类型 适用场景 问题 SideTables 解决方案
全局锁 所有对象的 retain/release 高并发下锁竞争严重 拆分 8 个分段锁(每个 SideTable 一个 slock),不同分段的操作互不阻塞
分段锁(slock 单个 SideTable 内的操作 同分段对象仍有竞争 分段数(8/16)是性能与内存的折中,冲突概率极低

示例:

  • 线程 A 操作「分段 0」的 obj1 → 加分段 0 的 slock;
  • 线程 B 操作「分段 1」的 obj2 → 加分段 1 的 slock;
  • 两个线程无锁竞争,并发执行。

3. 操作原子性流程

所有通过 SideTables 的操作(retain/release/storeWeak)都遵循「加锁→操作→解锁」:

1
2
3
4
5
① 哈希对象地址,找到对应的 SideTable;
② 加该 SideTable 的 slock 自旋锁;
③ 执行计数修改/弱引用注册;
④ 解锁;
⑤ 返回结果。

四、SideTables 在强引用(retain/release)中的核心流程

强引用的本质是修改对象的引用计数(retainCount),而计数的存储 / 修改完全依赖 SideTables

1. 引用计数的存储规则(isa 优化 + SideTables 补充)

存储位置 存储字段 适用场景
对象的 isa 指针 extra_rc(19 位) 引用计数 ≤ 10(64 位系统),直接存在 isa 中,无需访问 SideTables
SideTables::refcnts 补充计数值 计数 > 10、对象有弱引用 / 关联对象、计数溢出 isa 存储能力

2. retain 完整流程(基于 SideTables)

1
2
3
4
5
6
7
8
[obj retain]  
 哈希 obj 地址,找到 SideTables 中对应的 SideTable
 加该 SideTable  slock 锁;
 读取 obj->isa  `extra_rc`
   -  `extra_rc < 最大值`  `extra_rc += 1`
   -  `extra_rc 已满`  SideTables::refcnts[obj] += 1
 解锁;
 返回新的 retainCount

3. release 完整流程(基于 SideTables)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[obj release]  
 哈希 obj 地址,找到 SideTables 中对应的 SideTable
 加该 SideTable  slock 锁;
 读取 obj->isa  `extra_rc`
   -  `extra_rc > 0`  `extra_rc -= 1`
   -  `extra_rc == 0`  SideTables::refcnts[obj] -= 1
 检查总计数:
   - 若总计数 > 0  解锁,结束;
   - 若总计数 == 0  解锁,触发 obj->dealloc
 结束。

五、SideTables 在弱引用(weak)中的核心流程

弱引用的全生命周期(注册 / 移除 / 析构置 nil)都依赖 SideTables 的 weak_table

图片来源: Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t

1. storeWeak 中 SideTables 的作用(回顾)

1
2
3
4
5
6
7
8
storeWeak(&weakPtr, newObj) → 
① 哈希 oldObj 地址 → 找到 oldTable(SideTables[oldObj]);
② 哈希 newObj 地址 → 找到 newTable(SideTables[newObj]);
③ 加 oldTable/newTable 的 slock 锁(双锁,按地址排序防死锁);
④ 移除 oldTable::weak_table 中 weakPtr 的地址(weak_unregister_no_lock);
⑤ 注册 weakPtr 到 newTable::weak_table(weak_register_no_lock);
⑥ 解锁;
⑦ 返回 newObj。

2. 对象析构时 weak 置 nil(基于 SideTables)

1
2
3
4
5
6
7
8
obj->dealloc → 
① 哈希 obj 地址,找到 SideTables 中对应的 SideTable;
② 加该 SideTable 的 slock 锁;
③ 遍历 SideTable::weak_table 中 obj 对应的所有 weak 指针地址;
④ 将所有 weak 指针置为 nil(避免野指针);
⑤ 从 weak_table 中删除 obj 的条目;
⑥ 解锁;
⑦ 继续执行析构逻辑。

六、SideTables 的性能优化点

优化点 具体实现
分段哈希 + 分片锁 拆分为 8/16 个分段,每个分段独立加锁,大幅降低高并发下的锁竞争
isa 内存储计数 小计数直接存在 isa 中,减少对 SideTables 的访问(减少锁操作,提升性能)
位运算哈希 用 & (stripeCount-1) 替代取模,哈希计算耗时接近 0
自旋锁(slock 短操作(retain/release)用自旋锁,避免线程切换开销(比互斥锁快)
哈希表预分配 SideTablerefcnts/weak_table 预分配桶大小(4096),减少动态扩容开销

七、常见易错点与关键结论

  1. SideTables 不是只管弱引用:强引用计数的溢出存储、线程安全都依赖它,是强 / 弱引用的共同基石;
  2. 每个对象固定映射一个 SideTable:对象地址不变,映射关系永久不变,保证数据一致性;
  3. SideTables 是全局单例:进程内唯一,所有线程共享,通过分片锁保证安全;
  4. 自旋锁的适用场景slock 是自旋锁,仅适合 retain/release 这类毫秒级短操作,避免长时间占用导致 CPU 空转。

八、总结

SideTables 是 Objective-C 运行时的「心脏」:

  • 从功能上,它管理所有对象的生命周期(强引用计数)和弱引用指针;
  • 从性能上,它通过「分段哈希 + 分片锁」解决了全局锁竞争问题,支撑了高并发下的计数操作;
  • 从设计上,它兼顾了性能(isa 内存储)、安全(自旋锁)、扩展性(分段结构),是 Objective-C 内存管理的核心支柱。

Striped Map

一、Striped Map 核心定义

Striped Map(中文可称「分段哈希表」)是一种 基于「分片锁」思想设计的哈希表,核心目标是 降低多线程场景下的锁竞争,提升并发性能

Objective-C 运行时中,StripedMap<SideTable> 是全局存储 SideTable 的容器(即 SideTables() 函数返回的类型),其本质是:将全局哈希表拆分为多个独立的「段(Stripe)」,每个段对应一个 SideTable,且每个段自带独立的锁(SideTable 的 slock

二、为什么需要 Striped Map?(设计初衷)

如果不用 Striped Map,而是用「全局单锁哈希表」管理所有对象的 SideTable,会存在致命问题:

  • 所有线程对任何对象的 retain/release(强引用)、storeWeak(弱引用)操作,都会竞争同一把全局锁;
  • 高并发场景下(如多线程频繁操作不同对象),锁竞争会导致严重的性能瓶颈,甚至线程阻塞。

Striped Map 的解决方案:锁粒度细化将全局哈希表拆分为 N 个段(比如 iOS 中默认是 8 个段),每个段对应一个 SideTable,每个 SideTable 自带独立的 slock 自旋锁。这样:

  • 不同段的对象操作(如线程 A 操作段 0 的 obj1,线程 B 操作段 1 的 obj2),会使用各自段的锁,互不干扰,并发性能大幅提升;
  • 同一段的多个对象操作,仍会竞争该段的锁,但冲突概率远低于全局锁(段数越多,冲突概率越低)。

三、Striped Map 核心实现细节(Objective-C 底层)

1. 数据结构简化版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 模板类:StripedMap 是对任意类型 T 的分段哈希表包装(这里 T=SideTable)
template <typename T>
class StripedMap {
private:
    // 核心:分段数组(段数是 2 的幂,如 8/16,方便哈希取模)
    static const size_t stripeCount = 8; 
    T stripes[stripeCount]; // 每个元素是一个「段」(这里 T=SideTable,即每个段是一个 SideTable)

    // 哈希函数:将对象地址映射到具体的段索引
    size_t indexForPointer(const void *p) {
        // 取对象地址的哈希值,与 (stripeCount-1) 按位与,得到 0~stripeCount-1 的索引
        return ((uintptr_t)p >> 4) & (stripeCount - 1); 
    }

public:
    // 访问指定对象对应的段(SideTable)
    T& operator[] (const void *p) {
        return stripes[indexForPointer(p)];
    }
};

// 全局 SideTables:本质是 StripedMap<SideTable>
static StripedMap<SideTable>& SideTables() {
    static StripedMap<SideTable> sSideTables;
    return sSideTables;
}

2. 关键设计点解析

设计点 作用
段数(stripeCount 固定为 2 的幂(如 8),通过 & (stripeCount-1) 快速取模(比 % 运算高效)
哈希函数(indexForPointer 基于对象地址的高位(右移 4 位,避开低位噪声)计算索引,保证映射均匀性
分段数组(stripes) 每个元素是独立的 SideTable(带自己的 slock 锁、RefcountMapweak_table

3. 对象与 SideTable 的映射流程

当需要获取某个对象 obj 对应的 SideTable 时:

1
2
3
① 取 obj 的地址(void *p = obj);
② 调用 indexForPointer(p),通过哈希计算得到段索引(如 3);
③ 从 StripedMap 的 stripes 数组中取出索引 3 对应的 SideTable,即 obj 专属的 SideTable。

这个映射是固定的:同一对象的地址不变,每次都会映射到同一个 SideTable,保证引用计数、弱引用表的一致性。

四、Striped Map 与强 / 弱引用的关联(实战场景)

1. 强引用操作(retain/release)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
线程 A 调用 [obj1 retain] → 
① 通过 SideTables()[obj1] 找到 obj1 对应的 SideTable(段 0)→ 
② 加该 SideTable 的 slock 锁 → 
③ 修改引用计数(isa 的 extra_rc 或 RefcountMap)→ 
④ 解锁。

线程 B 调用 [obj2 release] → 
① 通过 SideTables()[obj2] 找到 obj2 对应的 SideTable(段 1)→ 
② 加该 SideTable 的 slock 锁 → 
③ 修改引用计数 → 
④ 解锁。

→ 两个线程同时操作,锁互不干扰,无竞争。

2. 弱引用操作(storeWeak)

1
2
3
4
5
6
7
线程 C 调用 storeWeak(&weakPtr, obj3) → 
① 通过 SideTables()[obj3] 找到 obj3 对应的 SideTable(段 2)→ 
② 加该 SideTable 的 slock 锁 → 
③ 注册 weak 指针到 weak_table → 
④ 解锁。

→ 与其他段的操作无竞争,并发安全。

五、核心优势与权衡

1. 核心优势

  • 高并发性能:锁粒度从「全局锁」细化到「段锁」,大幅减少锁竞争;
  • 高效访问:哈希取模通过位运算实现(& (stripeCount-1)),速度极快;
  • 实现简单:基于数组分段,结构清晰,无需复杂的动态扩容逻辑(段数固定)。

2. 权衡(设计取舍)

  • 段数固定:无法动态调整段数,若段数过少,高并发下仍可能出现同一段的锁竞争;若段数过多,会占用更多内存(每个段是一个 SideTable,包含锁、哈希表等结构);
  • 哈希冲突:不同对象可能映射到同一个段,导致同一段内的操作仍需竞争锁(但冲突概率远低于全局锁)。

六、iOS 底层的实际配置

  • 段数(stripeCount) :Objective-C 运行时中默认是 8(部分版本可能调整为 16),是兼顾并发性能和内存开销的折中值;
  • 适用场景:除了管理 SideTable,Striped Map 还用于其他需要高并发哈希表的场景(如关联对象的存储 _AssociatedObjects)。

七、总结

Striped MapObjective-C 底层为解决「全局哈希表锁竞争」而设计的核心数据结构,其本质是:通过「分段存储 + 分片锁」的思想,将全局锁拆分为多个段锁,在保证线程安全的前提下,最大化提升并发性能

它与 SideTable 深度绑定,共同支撑起 Objective-C 的强引用计数(retain/release)和弱引用(storeWeak)机制,是 iOS 底层线程安全和性能的关键保障之一。

参考链接

Built with Hugo
主题 StackJimmy 设计