iOS开发中常用的锁有如下几种:
1、@synchronized
2、NSLock 对象锁
3、NSRecursiveLock 递归锁
4、NSConditionLock 条件锁
5、pthread_mutex 互斥锁(C语言)
6、dispatch_semaphore 信号量实现加锁(GCD)
7、 OSSpinLock (暂不建议使用)
下图是它们的性能对比:
1、@synchronized 关键字加锁 互斥锁,性能较差不推荐使用
@synchronized(这里添加一个OC对象,一般使用self) {
这里写要加锁的代码
}
- 注意点:
1、加锁的代码尽量少
2、添加的OC对象必须在多个线程中都是同一对象
3、优点是不需要显式的创建锁对象,便可以实现锁的机制。
4、@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
2、NSLock对象锁
NSLock实际上是在pthread_mutex基础上进行了封装了,pthread_mutex的类型为 PTHREAD_MUTEX_ERRORCHECK,会有错误提示,同时会损失一定性能。
-
在Cocoa程序中NSLock中实现了一个简单的互斥锁。
所有锁(包括NSLock)的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法。你使用这些方法来获取和释放该锁。
NSLock类还增加了tryLock和lockBeforeDate:方法。
tryLock试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程,相反,它只是返回NO。
lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。
RunLoop[2212:106992] 线程1
RunLoop[2369:115459] 尝试加锁失败
tryLock 并不会阻塞线程。[lock tryLock] 能加锁返回 YES,不能加锁返回 NO,然后都会执行后续代码。
3、NSRecursiveLock 递归锁
- 使用锁最容易犯的一个错误就是在递归或循环中造成死锁。
- 如下代码中,因为在线程1中的递归block中,锁会被多次的lock,所以自己也被阻塞了
//创建锁
_mutexLock = [[NSLock alloc] init];
//线程1
dispatch_async(self.concurrentQueue, ^{
static void(^TestMethod)(int);
TestMethod = ^(int value){
[self.mutexLock lock];
if (value > 0){
[NSThread sleepForTimeInterval:1];
NSLog(@"%d",value);
TestMethod(value--);
}
[self.mutexLock unlock];
};
TestMethod(5);
});
此时被阻塞了。只会输出5。
- 此处将NSLock换成NSRecursiveLock,便可解决问题。
NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。
递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。
只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
//创建锁
_mutexLock = [[NSRecursiveLock alloc] init];
//线程1
dispatch_async(self.concurrentQueue, ^{
static void(^TestMethod)(int);
TestMethod = ^(int value){
[self.mutexLock lock];
if (value > 0){
NSLog(@"%d",value);
[NSThread sleepForTimeInterval:1];
TestMethod(--value);
}
[self.mutexLock unlock];
};
TestMethod(5);
});
4、NSConditionLock条件锁
条件锁,一个线程获得了锁,其它线程等待。
[xxxx lock]: 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁。
[xxx lockWhenCondition:A条件]:表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
[xxx unlockWithCondition:A条件]:表示释放锁,同时把内部的condition设置为A条件.
static NSInteger CONDITION_NO_DATA ; //条件一: 没有数据
static NSInteger CONDITION_HAS_DATA ; //条件二: 有数据
//初始化锁时,指定一个默认的条件
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:CONDITION_NO_DATA];
//生产者,加锁与解锁的过程
while (YES) {
//1. 当满足 【没有数据的条件时】进行加锁
[lock lockWhenCondition:CONDITION_NO_DATA];
//2. 生产者生成数据
//.....
NSLog(@"生产者生成数据");
//3. 解锁,并设置新的条件,已经有数据了
[lock unlockWithCondition:CONDITION_HAS_DATA];
}
//消费者,加锁与解锁的过程
while (YES) {
//1. 当满足 【有数据的条件时】进行加锁
[lock lockWhenCondition:CONDITION_HAS_DATA];
//2. 消费者消费数据
//.....
NSLog(@"消费者消费数据");
//3. 解锁,并设置新的条件,没有数据了
[lock unlockWithCondition:CONDITION_NO_DATA];
}
5、pthread_mutex 互斥锁
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
//线程1
dispatch_async(self.concurrentQueue, ^{
pthread_mutex_lock(&mutex);
NSLog(@"任务1");
sleep(2);
pthread_mutex_unlock(&mutex);
});
//线程2
dispatch_async(self.concurrentQueue, ^{
sleep(1);
pthread_mutex_lock(&mutex);
NSLog(@"任务2");
pthread_mutex_unlock(&mutex);
});
静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
动态初始化 pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为NULL,使用默认的属性,返回0代表初始化成功。这种方式可以初始化普通锁、递归锁(同NSRecursiveLock),初始化方式有些复杂
此类初始化方法可设置锁的类型,PTHREAD_MUTEX_ERRORCHECK 互斥锁不会检测死锁,PTHREAD_MUTEX_ERRORCHECK 互斥锁可提供错误检查,PTHREAD_MUTEX_RECURSIVE 递归锁,PTHREAD_PROCESS_DEFAULT 映射到 PTHREAD_PROCESS_NORMAL
6、信号量(Semaphore)
- 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任务1");
sleep(10);
dispatch_semaphore_signal(semaphore);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任务2");
dispatch_semaphore_signal(semaphore);
});
7、OSSpinLock
#import <libkern/OSAtomic.h>
//创建锁
_pinLock = OS_SPINLOCK_INIT;
//线程1
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
//线程2
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
- (void)saleTickets{
while (1) {
[NSThread sleepForTimeInterval:1];
//加锁
OSSpinLockLock(&_pinLock);
if (_tickets > 0) {
_tickets--;
NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票卖完了 Thread:%@",[NSThread currentThread]);
break;
}
//解锁
OSSpinLockUnlock(&_pinLock);
}
}
经YYKit作者确认,OSSpinLock已经不再线程安全,OSSpinLock有潜在的优先级反转问题.不再安全的 OSSpinLock;