SideTables 完全详解
SideTables 是 Objective-C 运行时全局核心枢纽,本质是 StripedMap<SideTable> 类型的单例对象,承担所有对象的强引用计数管理、弱引用表管理、线程安全锁三大核心职责,是 retain/release(强引用)、weak(弱引用)机制的底层基石。
|
|
一、SideTables 核心定位与本质
| 特性 | 说明 |
|---|---|
| 类型 | static StripedMap<SideTable>& SideTables()(全局单例,进程内唯一) |
| 核心作用 | 为所有对象分配专属的 SideTable,通过「分段哈希 + 分片锁」保证强 / 弱引用操作的线程安全与性能 |
| 设计初衷 | 解决全局锁竞争问题:将全局操作拆分为多个分段(Stripe),每个分段独立加锁,提升并发性能 |
补充:
SideTables不是单一的SideTable,而是分段哈希表容器(StripedMap),容器内包含多个独立的SideTable(默认 8 个,iOS部分版本为 16 个),每个SideTable对应一个「分段锁」和一组对象的计数 / 弱引用数据。
二、先吃透 SideTable(SideTables 的核心元素)
SideTables 的核心是数组形式存储的 SideTable 实例,每个 SideTable 是一个独立的「功能单元」,结构如下(简化版底层源码):
|
|
各组件详解
| 组件 | 数据类型 / 核心字段 | 核心作用 |
|---|---|---|
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,规则如下:
|
|
- 核心特性:同一对象的地址不变,永远映射到同一个
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)都遵循「加锁→操作→解锁」:
|
|
四、SideTables 在强引用(retain/release)中的核心流程
强引用的本质是修改对象的引用计数(retainCount),而计数的存储 / 修改完全依赖 SideTables:
1. 引用计数的存储规则(isa 优化 + SideTables 补充)
| 存储位置 | 存储字段 | 适用场景 |
|---|---|---|
对象的 isa 指针 |
extra_rc(19 位) |
引用计数 ≤ 10(64 位系统),直接存在 isa 中,无需访问 SideTables |
| SideTables::refcnts | 补充计数值 | 计数 > 10、对象有弱引用 / 关联对象、计数溢出 isa 存储能力 |
2. retain 完整流程(基于 SideTables)
|
|
3. release 完整流程(基于 SideTables)
|
|
五、SideTables 在弱引用(weak)中的核心流程
弱引用的全生命周期(注册 / 移除 / 析构置 nil)都依赖 SideTables 的 weak_table:
图片来源: Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t
1. storeWeak 中 SideTables 的作用(回顾)
|
|
2. 对象析构时 weak 置 nil(基于 SideTables)
|
|
六、SideTables 的性能优化点
| 优化点 | 具体实现 |
|---|---|
| 分段哈希 + 分片锁 | 拆分为 8/16 个分段,每个分段独立加锁,大幅降低高并发下的锁竞争 |
isa 内存储计数 |
小计数直接存在 isa 中,减少对 SideTables 的访问(减少锁操作,提升性能) |
| 位运算哈希 | 用 & (stripeCount-1) 替代取模,哈希计算耗时接近 0 |
自旋锁(slock) |
短操作(retain/release)用自旋锁,避免线程切换开销(比互斥锁快) |
| 哈希表预分配 | SideTable 的 refcnts/weak_table 预分配桶大小(4096),减少动态扩容开销 |
七、常见易错点与关键结论
- SideTables 不是只管弱引用:强引用计数的溢出存储、线程安全都依赖它,是强 / 弱引用的共同基石;
- 每个对象固定映射一个 SideTable:对象地址不变,映射关系永久不变,保证数据一致性;
- SideTables 是全局单例:进程内唯一,所有线程共享,通过分片锁保证安全;
- 自旋锁的适用场景:
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. 数据结构简化版
|
|
2. 关键设计点解析
| 设计点 | 作用 |
|---|---|
段数(stripeCount) |
固定为 2 的幂(如 8),通过 & (stripeCount-1) 快速取模(比 % 运算高效) |
哈希函数(indexForPointer) |
基于对象地址的高位(右移 4 位,避开低位噪声)计算索引,保证映射均匀性 |
分段数组(stripes) |
每个元素是独立的 SideTable(带自己的 slock 锁、RefcountMap、weak_table) |
3. 对象与 SideTable 的映射流程
当需要获取某个对象 obj 对应的 SideTable 时:
|
|
这个映射是固定的:同一对象的地址不变,每次都会映射到同一个 SideTable,保证引用计数、弱引用表的一致性。
四、Striped Map 与强 / 弱引用的关联(实战场景)
1. 强引用操作(retain/release)
|
|
2. 弱引用操作(storeWeak)
|
|
五、核心优势与权衡
1. 核心优势
- 高并发性能:锁粒度从「全局锁」细化到「段锁」,大幅减少锁竞争;
- 高效访问:哈希取模通过位运算实现(
& (stripeCount-1)),速度极快; - 实现简单:基于数组分段,结构清晰,无需复杂的动态扩容逻辑(段数固定)。
2. 权衡(设计取舍)
- 段数固定:无法动态调整段数,若段数过少,高并发下仍可能出现同一段的锁竞争;若段数过多,会占用更多内存(每个段是一个 SideTable,包含锁、哈希表等结构);
- 哈希冲突:不同对象可能映射到同一个段,导致同一段内的操作仍需竞争锁(但冲突概率远低于全局锁)。
六、iOS 底层的实际配置
- 段数(stripeCount) :Objective-C 运行时中默认是
8(部分版本可能调整为 16),是兼顾并发性能和内存开销的折中值; - 适用场景:除了管理 SideTable,Striped Map 还用于其他需要高并发哈希表的场景(如关联对象的存储
_AssociatedObjects)。
七、总结
Striped Map 是 Objective-C 底层为解决「全局哈希表锁竞争」而设计的核心数据结构,其本质是:通过「分段存储 + 分片锁」的思想,将全局锁拆分为多个段锁,在保证线程安全的前提下,最大化提升并发性能。
它与 SideTable 深度绑定,共同支撑起 Objective-C 的强引用计数(retain/release)和弱引用(storeWeak)机制,是 iOS 底层线程安全和性能的关键保障之一。
