锁的原理随笔
synchronized, NSLock, 递归锁, 条件锁
图中锁的性能从高到底依次是:OSSpinLock(自旋锁) -> dispatch_semaphone(信号量) -> pthread_mutex(互斥锁) -> NSLock(互斥锁) -> NSCondition(条件锁) -> pthread_mutex(recursive 互斥递归锁) -> NSRecursiveLock(递归锁) -> NSConditionLock(条件锁) -> synchronized(互斥锁)
自旋锁(OSSpinLock, atomic) -> 线程会反复检查变量是否可用 -> 忙等待 ->
会一直保持该锁 ->对于线程只会阻塞很短时间的场合是有效的.
互斥锁 (@synchronized , NSLock, pthread_mutex) -> 多线程编程 -> 防止两条线程同时对同一公共资源(例如全局变量)进行读写的机制 ->将代码切成一个个临界区.
条件锁 (NSCondition, NSConditionLock) ->条件变量 -> 资源要求不满足 -> 进入休眠, 资源被分配到->条件锁打开.
递归锁(pthread_mutex(recursive), NSRecursiveLock) ->同一个线程可以加锁N次而不会引发死锁 -> 特殊的互斥锁,即是带有递归性质的互斥锁
信号量(dispatch_semaphore) -> 更高级的同步机制 -> 互斥锁可以说是semaphore在仅取值0/1时的特例 ->实现更加复杂的同步
读写锁(特殊的自旋锁) -> 特殊的自旋锁 -> 一个读写锁同时只能有一个写者或者多个读者
- OSSpinLock -> 自旋锁之所以不安全,是因为获取锁后,线程会一直处于忙等待,造成了任务的优先级反转。->废弃 -> os_unfair_lock (加锁,休眠)
- atomic -> reallySetProperty -> spinlock_t加锁 -> 底层调用os_unfair_lock加锁 -> 防止哈希冲突 -> 加盐
- synchronized ->objc_sync_enter和objc_sync_exit
- 哈希表 ->SyncList -> 多线程
- SyncData - >链表 ->当前可重入
- tls线程缓存, cache缓存
- lockCount, threadCount ->递归互斥锁
总结:
- @synchronized -> 递归锁
- @synchronized ->可重入(lockCount , threadCount) -> 可嵌套
- @synchronized -> 链表
- 链表查询, 缓存查找以及递归 -> 耗内存 -> 耗性能 -> 性能低
- 方便简单, 不用解锁,使用频率高
- 不能对非OC对象加锁
- @synchronized (self) ->嵌套次数较少的场景 -> 锁住的对象也并不永远是self
8 嵌套次数较多 -> 锁self过多 -> 使用NSLock ,信号量
4. NSLock
NSLock -> 下层pthread_mutex的封装 -> 符号断点 -> Foundation ,但是不开源 ->借助Swift开源的Foundation分析
NSLock的性能仅次于pthead_mutex
5. pthread_mutex
互斥锁本身 -> 阻塞线程并睡眠
6. NSRecursiveLock
底层 -> pthread_mutex的封装 -> Swift的Foundation研究
NSLock 和 NSRecursiveLock底层几乎一样 ->NSRecursiveLock -> init -> 标识符PTHREAD_MUTEX_RECURSIVE
递归锁 -> 解决一种嵌套形式 -> 循环嵌套
7. NSCondition
条件锁 -> 锁和线程检查器
锁 -> 检查条件,保护数据源, 执行条件引发的任务
线程检查器 -> 根据条件是否运行线程
底层 -> Swift, Foundation -> pthread_mutex的封装
8. NSConditionLock
条件锁, 一个线程获得锁, 其他线程都要等待
NSConditionLock -> NSCondition + Lock
总结
OSSpinLock自旋锁由于安全性问题,在iOS10之后已经被废弃,其底层的实现用os_unfair_lock替代
使用OSSpinLock及所示,会处于忙等待状态
而os_unfair_lock是处于休眠状态
atomic原子锁自带一把自旋锁,只能保证setter、getter时的线程安全,在日常开发中使用更多的还是nonatomic修饰属性
atomic:当属性在调用setter、getter方法时,会加上自旋锁osspinlock,用于保证同一时刻只能有一个线程调用属性的读或写,避免了属性读写不同步的问题。由于是底层编译器自动生成的互斥锁代码,会导致效率相对较低
nonatomic:当属性在调用setter、getter方法时,不会加上自旋锁,即线程不安全。由于编译器不会自动生成互斥锁代码,可以提高效率
@synchronized在底层维护了一个哈希表进行线程data的存储,通过链表表示可重入(即嵌套)的特性,虽然性能较低,但由于简单好用,使用频率很高
NSLock、NSRecursiveLock底层是对pthread_mutex的封装
NSCondition和NSConditionLock是条件锁,底层都是对pthread_mutex的封装,当满足某一个条件时才能进行操作,和信号量dispatch_semaphore类似
锁的使用场景
如果只是简单的使用,例如涉及线程安全,使用NSLock即可
如果是循环嵌套,推荐使用@synchronized,主要是因为使用递归锁的 性能 不如 使用@synchronized的性能(因为在synchronized中无论怎么重入,都没有关系,而NSRecursiveLock可能会出现崩溃现象)
在循环嵌套中,如果对递归锁掌握的很好,则建议使用递归锁,因为性能好
如果是循环嵌套,并且还有多线程影响时,例如有等待、死锁现象时,建议使用@synchronized