工作中我们常常会遇到多线程的问题,比如滑动界面的同时播放音频、子线程请求网络数据并处理等等。当不同的线程同时处理一部分数据,或者不同的线程处理任务有顺序要求时,就需要用到各种锁来保证安全。
总的来说, 安全锁分为两类: 自旋锁和互斥锁。 它们的详细分类如下图:
自旋锁的实现原理是建立公用的临界区, 类似全局变量, 当一个线程在访问临界区内的资源时, 其它线程就需要等待, 直到临界区内的线程处理完毕离开, 才可以进入。 互斥锁也是建立公用临界区, 但是在访问已经占用的临界区时,访问线程会被睡眠, 直到公共区内的线程处理完才被唤醒。 两者的区别就像同样抢着去已经被占用的公共厕所, 自旋锁的处理方式是在厕所内把门锁上; 互斥锁厕所门口蹲着门卫, 直接把来的人干倒。 从实现方式上可以看出自旋锁的效率相对较高。下面介绍下各种锁的实现方式。
自旋锁
OSSpinLock
__block OSSpinLock theLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
OSSpinLockLock(&theLock);
NSLog(@"需要线程同步的操作1");
sleep(3);
OSSpinLockUnlock(&theLock);
});
查询API文档看到OSSpinLock在iOS10.0已经废除, 换成了os_unfair_lock。 查询相关资料了解到是由于OSSpinLock会由于线程优先级反转, 可能会不安全.
os_unfair_lock
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
os_unfair_lock_lock(&unfairLock);
NSLog(@"需要线程同步的操作1");
sleep(3);
os_unfair_lock_unlock(&unfairLock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
os_unfair_lock_lock(&unfairLock);
NSLog(@"需要线程同步的操作2");
sleep(1);
os_unfair_lock_unlock(&unfairLock);
});
打印的结果为:
2017-09-05 10:34:36.895281+0800 TimeTest[810:810984] 需要线程同步的操作2
2017-09-05 10:34:37.900904+0800 TimeTest[810:810977] 需要线程同步的操作1
可以看出优先级较高的线程确实会抢先申请锁资源。 但是在实际使用中, OSSpinLock不安全的情况却不多(暂时没有找到实际应用中不安全的场景)。
互斥锁
dispatch_semaphore_t
dispatch_semaphore_t signal;
signal = dispatch_semaphore_create(2);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"end task 1");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(2);
NSLog(@"end task 2");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(3);
NSLog(@"end task 3");
dispatch_semaphore_signal(signal);
});
dispatch_semaphore_create设置线程并发数signal, 上面的代码将并发数设置为2。 dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)将设置的并发数signal减1,接收dsema和timeout两个参数。 当signal的信号为0时,该线程会等待timeout的时间后执行后面的代码。 dispatch_semaphore_signal将并发数signal加1, 一般 和dispatch_semaphore_signal成对出现。 上面的例子中, task1和task2会在signal分配的2个线程中执行, task3会在task1或者task2执行完成后再进入分配的两个线程执行。
pthread_mutex_lock
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 定义锁的属性
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
sleep(2);
pthread_mutex_unlock(&mutex);
});
pthread_mutex_lock是NSLock, NSRecursiveLock的底层实现。 可以通过pthread_mutexattr_settype来设置创建锁的类型。
NSLock
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value){
[lock lock];
if (value > 0) {
NSLog(@"value = %zd", value);
sleep(2);
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});
上面的线程中的递归块RecursiveMethod,在调用lock方法后, 会在递归中再次调用lock方法申请锁资源,造成该线程睡眠而死锁。
*** -[NSLock lock]: deadlock ( '(null)')
*** Break on _NSLockError() to debug.
采用NSRecursiveLock可以解决。
NSRecursiveLock
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value){
[lock lock];
if (value > 0) {
NSLog(@"value = %zd", value);
sleep(2);
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});
NSConditionLock
NSConditionLock conditionClock = [[NSConditionLock alloc] initWithCondition:1];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionClock lock];
NSLog(@"Task1 begin");
sleep(2);
NSLog(@"Task1 end");
[conditionClock unlockWithCondition:10];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionClock lockWhenCondition:10];
NSLog(@"Task2 begin");
sleep(2);
NSLog(@"Task2 end");
[conditionClock unlockWithCondition:20];
});
conditionClock可以调用lock方法锁定线程, 或者调用lockWhenCondition方法, 在满足规定条件值时锁定线程, 用于不同线程间有数据交互的场景中。
时间对比
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 定义锁的属性
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
CFAbsoluteTime startTime_pthread = CFAbsoluteTimeGetCurrent();
NSUInteger count = 10000 * 100;
for (int i = 0; i < count; i++) {
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
}
CFAbsoluteTime endTime_pthread = CFAbsoluteTimeGetCurrent();
NSLog(@"time_pthread: %f", endTime_pthread - startTime_pthread);
CFAbsoluteTime startTime_syn = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < count; i++) {
@synchronized (self) {
}
}
CFAbsoluteTime endTime_syn = CFAbsoluteTimeGetCurrent();
NSLog(@"time_syn: %f", endTime_syn - startTime_syn);
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
CFAbsoluteTime startTime_unfairLock = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < count; i++) {
os_unfair_lock_lock(&unfairLock);
os_unfair_lock_unlock(&unfairLock);
}
CFAbsoluteTime endTime_unfairLock = CFAbsoluteTimeGetCurrent();
NSLog(@"time_unfairLock: %f", endTime_unfairLock - startTime_unfairLock);
结果:
time_pthread: 0.233672
time_syn: 0.591735
time_unfairLock: 0.101001
将pthread_mutex_lock、@ synchronized、 os_unfair_lock分别加锁、解锁100万次, 可以看到它们占用的时间@ synchronized要大大高于pthread_mutex_lock和os_unfair_lock, 也印证了开始对自旋锁性能高于互斥锁的结论。
实际使用中, 简单场景优先推荐使用自旋锁, 用到互斥锁的地方可以根据具体应用场景来选择。
喜欢和关注都是对我的鼓励和支持~