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. 读写锁 一种特殊的`自旋锁`,它把对共享资源的访问者划分成读者和写者 读者只对共享资源进行读访问,写者则需要对共享资源进行写操作 写: 获取写能力前,不能有任何读写: 如果已经有读操作,那么等待,直到没有读或者写 读: 可以多读: 当前资源只有读操作,那么可以访问。 当前已经有写操作,那么必须等待,直到没有写操作 可以使用[栅栏函数]完成读写锁的需求