4-24.【OC】【锁】Objective-C runtime 内部有哪些关键锁?

3 阅读3分钟

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(写操作),那么主线程就会因为等待这把全局锁而产生严重的卡顿。