概念
锁:在执行多线程时,用于强行限制资源访问的同步机制,即用于并发控制中保证对互斥要求的满足。
锁都是为了互斥(Mutual exclusion,缩写 Mutex)。即防止两条线程同时对同一公共资源(如全局变量)进行读写的机制。
分类
锁从实现上分两种:
- 自旋锁。效率高、安全性不足、占用CPU资源大。
- 非自旋锁。安全性突出、占用CPU资源小,但休眠、唤醒过程要消耗CPU资源。
自旋转锁
忙等待的,会一直在那空转(循环),直到使用锁的一方释放。自旋锁不会让线程状态发生切换,一直处于用户态,即线程一直都是active的,不会让线程进入阻塞/休眠,减少了不必要的上下文切换,执行速度快。
以下情况选用自旋锁:
- 预计线程等待锁的时间很短;
- 加锁代码(临界区)经常被调用,但竞争情况发生概率很小,对安全性要求不高;
- CPU资源不紧张或多核处理器
非自旋锁
非忙等待的,操作内核,将自己的状态改为阻塞挂起来,从待执行队列中移出,等待其他线程唤醒。在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获得锁的时候需要从内核态恢复,需要线程上下文切换,影响锁的性能。
以下情况使用非自旋锁:
- 预计线程等待锁的时间比较长;
- 单核处理器;
- 临界区有IO操作;
- 临界区代码复杂度、循环量大
- 临界区竞争非常激烈,对安全性要求高
阻塞与休眠
- 阻塞:等待一个中断事件的到来
- 休眠:等待一个超时事件的到来
iOS中的锁
互斥锁:
- pthread_mutex_t
- NSLock、NSConditionLock(封装了pthread_mutex_t,attr = 普通)
- NSDistributedLock(封装了pthread_mutex_t,attr = 递归)
- 引用计数表的数据结构中使用到,对一张表的多个部分进行同时操作。
- NSCondition(封装了pthread_mutex_t和pthread_cond_t)
- @synchronized
递归锁(基于互斥锁):
- NSRecursiveLock
自旋锁:
- OSSpinLock
NSLock
普通的互斥锁。通过阻塞线程实现。
NSConditionLock
条件锁。比NSLock多了个NSInteger condition
作为相等的条件。与condition
相等则加锁。
NSRecursiveLock
递归锁。与NSLock类似,但可以在同一线程重复加锁而不死锁。实现递归过程原子性。
OSSpinLock
自旋锁。
os_unfair_lock
用于替代OSSpinLock,解决了优先级反转的问题,但其本质是互斥锁。atomic内部也使用该锁。
NSCondition
协调线程间的顺序执行。wait-signal。先执行一部分任务,然后跳转到其他地方执行,完了以后再回来。
@synchronize(object)
通过判断传入的对象是否相同,才满足互斥。
dispatch_semaphore
信号量。限制有限数量的资源使用。
pthread_mutex
互斥锁。
实现多线程多读单写
实现方案:
- pthread_rwlock,读写锁
- dispatch_barrier_async,异步栅栏调用
- 需要在一个自己创建的并发队列中执行屏障。
1 | //手动创建一个并发队列 |