iOS中常见的锁有哪些?
@synchronized
,信号量dispatch_semaphore_t
,os_unfair_lock
,实现了NSLocking
的几个类(NSLock
,NSRecursiveLock
,NSCondition
,NSConditionLock
)
上面提到的都是互斥锁(即线程在尝试访问共享资源的时候,发现有锁,就休眠进行等待唤醒)。
关于锁的性能
网上有很多讨论锁性能的文章,但是我觉得这个大可不必。
首先肯定是功能简单,越贴近底层的锁性能最高。
比如实现了递归功能的锁性能一定是比不实现递归的锁要性能高。
但是抛开功能来谈性能,这不是有点无聊吗?
其次,网上的demo里都是在一个for循环里面,先加锁再解锁,没有考虑到多线程的情况。
我也不知道这样得出来的性能对比是否有意义。
拿了网上的demo,跑了一下。
循环在1万次
的时候,可以看到各个锁的性能差别不大。最好的与最坏的差了三百多毫秒
。
因此我觉得在日常开发过程中,其实可以不考虑锁的性能,把精力放到功能实现上即可。
锁的一些简单介绍
@synchronized
使用代码一般如下:
@synchronized** ( self ) {
// do something
}
功能非常强大。可以在多线程中保证数据安全,同时也支持递归使用。
内部的实现流程大致是(不考虑缓存的情况):
-
加锁之前,先从线程的
TLS
存储空间中尝试获取数据data
-
如果数据data存在,并且
锁标识
和当前传入的锁标识
一样,那么就说明当前线程已经有锁,这个时候将lockcount
计数加1,然后结束流程,进行mutex加锁操作。 -
走到这里,说明没有
data
或者锁标识
不一致。 -
没有data的情况,直接创建data,并将
threadcount
计数加1,结束流程,进行mutex加锁操作。 -
如果有data,只是锁的标识不一致,那就创建新的data,将其插入到链表的头部,结束流程,进行mutex加锁操作。
NSLock
是一个相对来说比较简单的互斥锁。
使用的时候需要注意:
-
不支持递归,即同一条线程不能在未解锁之前进行加锁操作。
-
加锁和解锁的操作必须在同一条线程进行
NSRecursiveLock/ pthread_mutex(recursive)
可以实现递归加锁功能。
NSCondition / NSConditionLock
在锁的基础上可以追加一些条件。
NSCondition
的使用如下:
NSConditionLock
使用如下:
可以看到,当condition
不满足的时候,当前执行线程会被卡住,直到条件满足后继续执行。
dispatch_semaphore
利用
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
和dispatch_semaphore_signal(dispatch_semaphore_t dsema);
来实现锁的效果。
dispatch_semaphore_wait
会将信号量减去1,如果此时信号量值小于0,就会卡住线程,等待唤醒
dispatch_semaphore_signal
会将信号量加1,如果之前的信号量小于0,就会唤醒之前等待的线程。
os_unfair_lock
用来替代OSSpinLock
,因为OSSpinLock存在优先级反转的问题,已经被弃用了。
OSSpinLock弃用原因:
高优先级线程始终在低优先级线程前执行。如果一个低优先级的线程先获得了锁并访问共享资源, 这个时候高优先级线程也尝试获得锁,就会出现高优先级线程在等待锁, 而低优先级线程在等高优先级线程用完CPU
值得注意的是os_unfair_lock
是互斥锁,OSSpinLock
是自旋锁。
读写锁
很早之前记得有个面试题,面试官让我实现:
-
同一时间,只能有一个线程进行setter操作;
-
同一时间,允许多个线程进行getter操作;
-
同一时间,setter和getter操作互斥;
前不久看一个视频才明白这是一个读写锁的实际场景。
解决方案是利用栅栏函数+同步函数