- 谈下iOS开发中知道的哪些锁? 哪个性能最差?SD和AFN使用的哪个? ⼀般开发中你最常用哪个? 哪个锁apple存在问题又是什么问题?
使用锁注意
- 成对使用
- 不能递归的锁,避免嵌套使用,造成死锁
互斥与自旋
NSLock是基于 POSIX threads 实现的,而 POSIX threads 中使用互斥量同步线程。
互斥量(或称为互斥锁)是 pthread 库为解决这个问题提供的一个基本的机制。互斥量是一个锁,它保证如下三件事情:
原子性 - 锁住一个互斥量是一个原子操作,表明操作系统保证如果在你已经锁了一个互斥量,那么在同一时刻就不会有其他线程能够锁住这个互斥量;
奇异性 - 如果一个线程锁住了一个互斥量,那么可以保证的是在该线程释放这个锁之前没有其他线程可以锁住这个互斥量;
非忙等待 - 如果一个线程(线程1)尝试去锁住一个由线程2锁住的锁,线程1会挂起(suspend)并且不会消耗任何CPU资源,直到线程2释放了这个锁。这时,线程1会唤醒并继续执行,锁住这个互斥量。
OSSpinLock
OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
一般的锁如果第一次获得到了,后续线程再想获得就获取不到,它会释放当前所持有的资源,然后对自己进行一个阻塞行为,而自旋锁
会循环等待询问,并不释放资源. 类似于一个while循环 ,一直在访问能否获取当前锁, 如果不能获得它就继续轮询, 直到有一次能获得锁.
用于轻量级访问,简单的int值+1/-1操作
目前已经不再安全,可能会出现优先级反转问题
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
需要导入头文件#import <libkern/OSAtomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁(如果需要等待就不加锁,直接返回fasle;如果不需要等待就加锁,返回true);
bool result = OSSpinLockTry(&lock);
//加锁
OSSpinLockLock(&lock);
//数据的读写操作
//.....do something
//解锁
OSSpinLockUnlock(&lock);
os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10+开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
需要导入头文件#import <os/lock.h>
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//尝试加锁
bool result = os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
NSLock
NSLock是对mutex普通锁的封装
NSRecursiveLock
NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致
NSCondition
NSCondition是对mutex和cond的封装
NSConditionLock
NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
pthread_mutex
mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
需要导入头文件#import <pthread.h>
dispatch_semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//信号量的初始值
int value = 1;
//初始化信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
//如果信号量的值<=0,当前线程就会进入休眠等待,(直到信号量的值>0)
//如果信号量的值>0,就减1,然后往下执行后面的代码.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//让信号量的值+1
dispatch_semaphore_signal(semaphore);
创建信号量大致为创建一个对应的结构体
struct semaphore{
int value; //信号量
List<thread>;//线程控制表
}
dispatch_semaphore_wait(...){
S.value = S.value - 1;
if(S.value < 0){
//主动阻塞行为
Block(S.list);
}
}
dispatch_semaphore_signal(...){
S.value = S.value + 1;
//列表中需要有相应的线程需要唤醒
///唤醒是一个被动行为
if S.value <= 0{
wakeup(S.List);
}
}
dispatch_queue
直接使用GCD的串行队列,也是可以实现线程同步的
dispatch_queue_t queue = dispatch_queue_create("com.serial.queue",DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue,^{
//任务
});
@synchronized
一般在创建单例对象的时候使用,来保证在多线程环境下创建的对象是唯一的.
@synchronized是对mutex递归锁的封装
源码查看:objc4中的objc-sync.mm文件
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作
@synchronized(obj){
}
atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)
atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁
可以参考源码objc4的objc-accessors.mm
它并不能保证使用属性的过程是线程安全的
@property (atomic)NSMutableArray array;
self.array = [[NSMutableArray alloc]init];✅
[self.array addObject:obj];❎
NSLock
一般是用来解决细粒度的线程同步问题,来保证各个线程互斥,进入自己的临界区.
不可以递归,重入会造成死锁
[lock lock];
...操作逻辑
[lock unlock];
NSRecursiveLock
递归锁,可以重入.
性能由高到低
- os_unfair_lock
- OSSpinLock
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈
问题
SD中使用的是@synchronized和信号量
#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#define UNLOCK(lock) dispatch_semaphore_signal(lock);
AFN中使用的@synchronized和信号量
OSSpinLock的不安全问题