Objective-C Runtime(libobjc)作为一个高度动态且线程安全的系统,其内部大量使用了不同类型的锁来保护全局表、方法缓存和类结构。
了解这些内部锁,能帮你理解为什么有些看起来简单的操作(如 [obj class] 或动态添加方法)在极端高并发下也可能成为瓶颈。
1. runtimeLock (全局读写锁)
这是 Runtime 中最核心的锁。在源码中它是一个 un_fair_lock 的变体(或 pthread_rwlock_t),用于保护所有的类结构。
- 保护对象:已注册的类列表(gdb_objc_realized_classes)、协议、分类的加载等。
- 触发场景:类加载(
_read_images)、动态添加方法(class_addMethod)、关联对象(objc_setAssociatedObject的某些版本)以及处理realizeClass逻辑。 - 特性:它是读写锁。多线程读取类信息是并发的,但一旦有新的类加载或方法注入,就会触发写锁,阻塞其他线程。
2. cacheUpdateLock (方法缓存锁)
专门用于保护每个类的 cache_t(方法缓存)。
- 保护对象:方法选择器(SEL)到函数实现(IMP)的缓存哈希表。
- 触发场景:当
objc_msgSend在缓存中找不到方法并查询方法列表后,需要将结果写入缓存时。 - 为什么重要:方法缓存的写入必须是线程安全的,否则在高并发调用时可能导致哈希表损坏或读取到错误的 IMP。
3. AssociationsManagerLock (关联对象锁)
用于管理所有通过 objc_setAssociatedObject 绑定的数据。
- 保护对象:全局的关联对象哈希表(
AssociationsHashMap)。 - 触发场景:设置、获取或移除关联对象时。
- 隐蔽风险:所有的关联对象(不论属于哪个对象实例)共用这一把全局锁。如果你在大量并发线程中频繁操作关联对象,这把锁会成为全局瓶颈。
4. class_rw_t 内部锁
每个类在运行时都有一个 class_rw_t 结构体,用于存储方法列表、属性等。
- 保护对象:类的方法列表(method lists)。
- 触发场景:Category 加载、Method Swizzling。
- 特点:在现代 Runtime 版本中,为了优化,某些操作使用了更细粒度的原子操作,但在修改方法列表结构时仍需加锁。
5. @synchronized 锁池 (SyncData)
当你使用 @synchronized(obj) 时,Runtime 内部并不是直接给 obj 加锁。
- 保护对象:一个全局的
StripedMap<SyncList>,通过对象地址哈希找到对应的SyncData。 - 特性:这是一种递归互斥锁。
- 隐蔽死锁:如果多个对象哈希到了同一个
SyncList槽位,即使它们是不同的对象,也可能产生竞争。
总结对照表
| 锁名称 | 类型 | 保护内容 | 对开发者的影响 |
|---|---|---|---|
| runtimeLock | 读写锁 | 全局类/协议列表 | 插件/热修复频繁改动类时影响性能 |
| cacheUpdateLock | 互斥锁 | 方法缓存 | 影响首次调用方法的速度 |
| AssociationsLock | 互斥锁 | 全局关联对象表 | 高并发设置关联对象会产生全局阻塞 |
| SyncData Lock | 递归锁 | @synchronized 逻辑 | 大量使用时可能因哈希冲突导致竞争 |
关联到您的 slowlog_0
在您的日志中,GeTuiSdk 在初始化时触发了大量模型转换(GTCModel),这必然涉及大量的 Runtime 反射(如获取属性列表、方法列表)。
此时,个推 SDK 频繁触发了
runtimeLock的读操作。如果在同一时间你的应用还在动态加载插件或执行 Swizzling(写操作),那么主线程就会因为等待这把全局锁而产生严重的卡顿。