之前的文章我们详细介绍了iOS中使用多线程的方式,但是一直没有细致的讲解如何在多线程的情况下保证线程的安全,今天我们就介绍下iOS中多线程中保证线程安全的方式之一锁机制
在具体说这些锁之前,先来说几个概念定义:(参考维基百科)
- 临界区:指的是一块对公共资源进行访问的代码,并非一种机制或是算法。
- 自旋锁:是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
- 互斥锁(Mutex):是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。
- 读写锁(共享-互斥锁):是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。 读写锁通常用互斥锁、条件变量、信号量实现。
- 信号量(semaphore):是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
- 条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
本文主要介绍以下11种锁的使用方式,对原理性质介绍较少,针对每种锁的工作机制,大家可以自行科普。
- 互斥锁
- NSLock
- pthread_mutex
- @synchronized
- 自旋锁
- OSSpinLock
- os_unfair_lock
- 读写锁(共享-互斥锁)
- pthread_rwlock
- 递归锁
- NSRecursiveLock
- pthread_mutex(recursive)
- 条件锁
- NSCondition
- NSConditionLock
- 信号量
- dispatch_semaphore
- 性能对比
互斥锁
NSLock
NSLock:是Foundation框架中以对象形式暴露给开发者的一种锁,(Foundation框架同时提供了NSConditionLock,NSRecursiveLock,NSCondition)NSLock定义如下:
1 | @protocol NSLocking |
tryLock 和 lock 方法都会请求加锁,唯一不同的是trylock在没有获得锁的时候可以继续做一些任务和处理。lockBeforeDate方法也比较简单,就是在limit时间点之前获得锁,没有拿到返回NO。
实际项目中:NSLock在AFNetworking的AFURLSessionManager.m中应用如下:
1 | - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { |
pthread_mutex
实际项目中: 在YYKit的YYMemoryCach中可以看到
1 | - (instancetype)init { |
@synchronized:
实际项目中:AFNetworking中 isNetworkActivityOccurring属性的getter方法
1 | - (BOOL)isNetworkActivityOccurring { |
关于 @synchronized推荐扩展阅读 关于 @synchronized,这儿比你想知道的还要多
自旋锁
OSSpinLock:
1 | OSSpinLock lock = OS_SPINLOCK_INIT; |
上面是OSSpinLock使用方式,编译会报警告,已经废弃了,OSSpinLock大家也已经不再用它了,因为它在某一些场景下已经不安全了,可以参考 YY大神的不再安全的 OSSpinLock,在Protocol Buffers项目中你可以看到这样的注释,大家已经用新的方案替换了。
1 | // NOTE: OSSpinLock may seem like a good fit here but Apple engineers have |
os_unfair_lock:
os_unfair_lock 是苹果官方推荐的替换OSSpinLock的方案,但是它在iOS10.0以上的系统才可以调用。
1 | os_unfair_lock_t unfairLock; |
读写锁(共享-互斥锁)
pthread_rwlock:
1 | //加读锁 |
递归锁
递归锁有一个特点,就是同一个线程可以加锁N次而不会引发死锁。
NSRecursiveLock:
NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到:
1 | _lock = [NSRecursiveLock new]; |
pthread_mutex(recursive):
pthread_mutex锁也支持递归,只需要设置PTHREAD_MUTEX_RECURSIVE即可
1 | pthread_mutex_t lock; |
条件锁
NSCondition:
1 | @interface NSCondition : NSObject { |
遵循NSLocking协议,使用的时候同样是lock,unlock加解锁,wait是傻等,waitUntilDate:方法是等一会,都会阻塞掉线程,signal是唤起一个在等待的线程,broadcast是广播全部唤起。
1 | NSCondition *lock = [[NSCondition alloc] init]; |
NSConditionLock:
1 | @interface NSConditionLock : NSObject { |
信号量
dispatch_semaphore:
dispatch_semaphore在YYKit中的YYThreadSafeArray.m有所应用,YY大神有这样一句注释:
1 | @discussion Generally, access performance is lower than NSMutableArray, |
1 | #define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ |
性能对比
参考网上另外一篇文章中所测的性能测试结果
LockPerformance.jpg
- 注:运行手机: iphone6s plus ,系统版本:11.2.2,Xcode9.2;数字的单位为ns(得出的具体数值是跑了多次取的均值)。
本人对性能的看法是,在锁使用不太频繁的情况不用太在意这些性能之间的微小差异,在使用锁非常频繁的场景下,需要按照特定场景选择适合该场景的锁,既然这些锁都被一直沿用,肯定是在某些特殊场景下有最适合它发挥的情况。