iOS锁
多线程下写操作可能导致的问题,锁住资源,等待
在iOS中锁的基本种类只有两种
:`互斥锁`、`自旋锁`
变相的,你可以通过 串行队列,栅栏函数 等实现类似加锁能力
加锁性能排行:
信号量 > 互斥锁pthread_mutex > NSLock > 条件锁 > @synchronized
互斥锁
发现资源被其它线程锁住后,线程会进入睡眠,等待锁释放时被唤醒
递归锁
可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用
非递归锁
不可重入,必须等锁释放后才能再次获取锁
1.pthread_mutex
// 导入头文件
#import <pthread.h>
// 全局声明互斥锁
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
// 加锁
pthread_mutex_lock(&_lock);
// 这里做需要线程安全操作
// ...
// 解锁
pthread_mutex_unlock(&_lock);
// 释放锁
pthread_mutex_destroy(&_lock);
YYKit的YYMemoryCach, 使用到`pthread_mutex` 封装
2.@synchronized
通过**汇编**能发现`@synchronized`就是实现了`objc_sync_enter`和
`objc_sync_exit`两个方法
性能较差,使用比较方便
是递归锁, 可以在释放锁之前,继续获取锁权限,但是最好不要嵌套太深,影响
性能
不能使用`非OC对象`作为加锁条件——底层`id2data`中接收参数为id类型
案例
- (void)test {
_testArray = [NSMutableArray array];
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (self.testArray) {
self.testArray = [NSMutableArray array];
}
});
}
}
上述代码会crash,因为在某一瞬间`testArray`释放了为nil
可以对self加锁,但是又太重了,锁self也存在异常到时候
可以创建一个静态变量 static ,一个synchronized使用一个对这个静态变量加
锁,其它线程也能访问到这个变量
加锁原理类似 weak 哈希表,把对象映射到一张hash表上,记录被锁的次数,这
样可以适用递归场景
空对象在准备加锁前会判断,这样保证不会对nil加锁,但是加锁以后对象被释放
是不行的
缓存(表映射)取对象不会导致死锁
3.NSLock
本质就是对pthread_mutex 互斥锁的封装
非递归锁,不适应递归场景,会堵塞线程
使用互斥锁`NSLock`异步并发调用block块,block块内部递归调用自己,
那么就会线程堵塞
**解决方案:** 使用递归锁`NSRecursiveLock`替换`NSLock`
4.NSRecursiveLock
- (void)test {
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^block)(int);
block = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value——%d", value);
block(value - 1);
}
[lock unlock];
};
block(10);
});
}
NSRecursiveLock`在[YYKit中YYWebImageOperation.m]中有用到
死锁
for循环在block内部对同一个对象进行了多次锁操作,直到这个资源身上挂着N
把锁,最后大家都无法一次性解锁——找不到解锁的出口
**解决:** 可以采用使用缓存的`@synchronized`,因为它对对象进行锁操
作,会先从缓存查找是否有锁`syncData`存在。如果有,直接返回而不加锁,
保证锁的唯一性
5.dispatch_semaphore
GCD信号量
通过控制数值
6.NSCondition
是一个条件锁
与信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至
条件满足
7.NSConditionLock
open func lock() {
let _ = lock(before: Date.distantFuture)
}
`NSConditionLock`是`NSCondition`加线程数的封装
`NSConditionLock`可以设置锁条件,而`NSCondition`只是无脑的通知信号
8.os_unfair_lock
由于`OSSpinLock`自旋锁的bug,替代方案是内部封装了`os_unfair_lock`,
而`os_unfair_lock`在加锁时会处于休眠状态,而不是自旋锁的忙等状态
自旋锁
线程反复检查锁变量是否可⽤,⼀种`忙等待`。
消耗CPU,因此对于线程只会阻塞很短时间的场合是有效的
避免了进程上下⽂的调度开销
1.OSSpinLock
出现了安全问题之后就废弃了
`OSSpinLock`忙等的机制就可能造成高优先级一直`running等待`,占用CPU
时间片;而低优先级任务无法抢占时间片,变成迟迟完不成,不释放锁的情况
2.atomic
内部使用`os_unfair_lock`替代了`OSSpinLock`(iOS10之后替换)
只是锁set,get方法,并完全非线程安全
所以现在atomic是 基于 互斥锁 os_unfair_lock 实现的了
3. 读写锁
一种特殊的`自旋锁`,它把对共享资源的访问者划分成读者和写者
读者只对共享资源进行读访问,写者则需要对共享资源进行写操作
写:
获取写能力前,不能有任何读写:
如果已经有读操作,那么等待,直到没有读或者写
读:
可以多读:
当前资源只有读操作,那么可以访问。
当前已经有写操作,那么必须等待,直到没有写操作
可以使用[栅栏函数]完成读写锁的需求