IOS编码中,锁的出现其实是因为多线程安全的问题。那么,问题来了,什么是线程安全?为什么锁可以解决线程安全问题?单线程是不是绝对的线程安全?iOS编程有多少种锁?加解锁的效率如何?......

什么线程安全?
多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。
NSInteger total = 0;
- (void)threadNotSafe {
for (NSInteger index = 0; index < 3; index++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
total += 1;
NSLog(@"total: %ld", total);
total -= 1;
NSLog(@"total: %ld", total);
});
}
}
//第一次输出:
2017-11-28 23:34:11.551570+0800 BasicDemo[75679:5312246] total: 1
2017-11-28 23:34:11.551619+0800 BasicDemo[75679:5312248] total: 3
2017-11-28 23:34:11.551618+0800 BasicDemo[75679:5312249] total: 2
2017-11-28 23:34:11.552120+0800 BasicDemo[75679:5312246] total: 2
2017-11-28 23:34:11.552143+0800 BasicDemo[75679:5312248] total: 1
2017-11-28 23:34:11.552171+0800 BasicDemo[75679:5312249] total: 0
//第二次输出
2017-11-28 23:34:55.738947+0800 BasicDemo[75683:5313401] total: 1
2017-11-28 23:34:55.738979+0800 BasicDemo[75683:5313403] total: 2
2017-11-28 23:34:55.738985+0800 BasicDemo[75683:5313402] total: 3
2017-11-28 23:34:55.739565+0800 BasicDemo[75683:5313401] total: 2
2017-11-28 23:34:55.739570+0800 BasicDemo[75683:5313402] total: 1
2017-11-28 23:34:55.739577+0800 BasicDemo[75683:5313403] total: 0
NSInteger total = 0;
NSLock *lock = [NSLock new];
- (void)threadSafe {
for (NSInteger index = 0; index < 3; index++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
total += 1;
NSLog(@"total: %ld", total);
total -= 1;
NSLog(@"total: %ld", total);
[lock unlock];
});
}
}
//第一次输出
2017-11-28 23:35:37.696614+0800 BasicDemo[75696:5314483] total: 1
2017-11-28 23:35:37.696928+0800 BasicDemo[75696:5314483] total: 0
2017-11-28 23:35:37.696971+0800 BasicDemo[75696:5314481] total: 1
2017-11-28 23:35:37.696995+0800 BasicDemo[75696:5314481] total: 0
2017-11-28 23:35:37.697026+0800 BasicDemo[75696:5314482] total: 1
2017-11-28 23:35:37.697050+0800 BasicDemo[75696:5314482] total: 0
//第二次输出
2017-11-28 23:36:01.790264+0800 BasicDemo[75700:5315159] total: 1
2017-11-28 23:36:01.790617+0800 BasicDemo[75700:5315159] total: 0
2017-11-28 23:36:01.790668+0800 BasicDemo[75700:5315161] total: 1
2017-11-28 23:36:01.790687+0800 BasicDemo[75700:5315161] total: 0
2017-11-28 23:36:01.790711+0800 BasicDemo[75700:5315160] total: 1
2017-11-28 23:36:01.790735+0800 BasicDemo[75700:5315160] total: 0
第一个函数和第一次和第二次调用的函数不一样,换句话说,不能够确认代码的执行顺序和结果,是线程不安全的;第二个函数的第一次和第二次输出结果是一样的,可以确定函数的执行结果,是线程安全的。
锁
线程不安全是有多线程访问造成的,那么如何解决?
- 既然线程安全问题是由多线程引起的,那么最极端的做法使用单线程保证线程安全。
- 线程安全是由于多线程访问修改共享资源引起不可预测的结果,因此,如果都是访问共享资源而不去修改共享资源也可以保证线程安全,比如:设置属性的全局变量。
- 使用锁。
自旋锁(OSSpinLock)
#pragma mark - 自旋锁,已经被废弃
- (void)ossPinLockConfig{
osslock = OS_SPINLOCK_INIT;
tickets = 5;
// 线程1
dispatch_queue_t diapatchQueue0 =dispatch_queue_create("diapatchQueue0", DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_async(diapatchQueue0, ^{
[self saleTickets];
});
//线程2
dispatch_queue_t diapatchQueue1 =dispatch_queue_create("diapatchQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(diapatchQueue1, ^{
[self saleTickets];
});
}
- (void)saleTickets {
while (1) {
[NSThread sleepForTimeInterval:1];
//加锁
OSSpinLockLock(&osslock);
if (tickets > 0) {
tickets--;
NSLog(@"剩余票数= %ld, Thread:%@",tickets,[NSThread currentThread]);
} else {
NSLog(@"票卖完了 Thread:%@",[NSThread currentThread]);
break;
}
//解锁
OSSpinLockUnlock(&osslock);
}
}
输出结果
2018-01-02 16:11:15.727776+0800 LockTest[3136:239407] 剩余票数= 4, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:15.727970+0800 LockTest[3136:239404] 剩余票数= 3, Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
2018-01-02 16:11:16.732482+0800 LockTest[3136:239407] 剩余票数= 2, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:16.732632+0800 LockTest[3136:239404] 剩余票数= 1, Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
2018-01-02 16:11:17.735044+0800 LockTest[3136:239407] 剩余票数= 0, Thread:<NSThread: 0x60c000261680>{number = 3, name = (null)}
2018-01-02 16:11:17.735261+0800 LockTest[3136:239404] 票卖完了 Thread:<NSThread: 0x60000007f100>{number = 4, name = (null)}
自旋锁面临的问题不再安全的 OSSpinLock:
OSSpinLock存在的bug:新版iOS 中,系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。
具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于spin lock 的忙等状态从而占用大量CPU。此时低优先级线程无法与高优先级线程争夺CPU 时间,从而导致任务迟迟完不成、无法释放lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock。
苹果工程师 Greg Parker提到,对于这个问题,一种解决方案是用 truly unbounded backoff算法,这能避免livelock问题,但如果系统负载高时,它仍有可能将高优先级的线程阻塞数十秒之久;另一种方案是使用 handoff lock 算法,这也是libobjc目前正在使用的。锁的持有者会把线程 ID 保存到锁内部,锁的等待者会临时贡献出它的优先级来避免优先级反转的问题。理论上这种模式会在比较复杂的多锁条件下产生问题,但实践上目前还一切都好。
libobjc里用的是Mach 内核的 thread_switch()然后传递了一个 mach thread port 来避免优先级反转,另外它还用了一个私有的参数选项,所以开发者无法自己实现这个锁。另一方面,由于二进制兼容问题,OSSpinLock 也不能有改动。
最终的结论就是,除非开发者能保证访问锁的线程全部都处于同一优先级,否则 iOS 系统中所有类型的自旋锁都不能再使用了。
模拟OSSpinLock高低优先级场景
#pragma mark - 自旋锁,已经被废弃
- (void)ossPinLockConfig{
osslock = OS_SPINLOCK_INIT;
tickets = 1000;
// 线程1
// dispatch_queue_t diapatchQueue0 =dispatch_queue_create("diapatchQueue0", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t diapatchQueue0 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue0, ^{
[self saleTickets:@"2"];
});
//线程2
// dispatch_queue_t diapatchQueue1 =dispatch_queue_create("diapatchQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t diapatchQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(diapatchQueue1, ^{
[self saleTickets:@"1"];
});
dispatch_queue_t diapatchQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue2, ^{
[self saleTickets:@"3"];
});
dispatch_queue_t diapatchQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue3, ^{
[self saleTickets:@"4"];
});
for (NSInteger i = 0; i < 100000000; i ++) {
dispatch_queue_t diapatchQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(diapatchQueue4, ^{
[self saleTickets:@"5"];
});
}
}
- (void)saleTickets:(NSString *)flag {
while (1) {
[NSThread sleepForTimeInterval:1];
//加锁
OSSpinLockLock(&osslock);
if (tickets > 0) {
tickets--;
// 如果flag 等于1的情况下阻塞线程
if ([flag isEqualToString:@"1"]) {
sleep(4);
}
NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票卖完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
//解锁
OSSpinLockUnlock(&osslock);
}
}
我们模拟1亿个人去访问同一个资源,其中flag = 1的线程是低优先级的线程,在资源访问的时候,我们又对线程1访问资源时候做一个线程阻塞4s,观察CPU Report 中Usage ComParison Other processes 的变化,在阻塞的过程中Other processes占比会猛然增加,说明其余优先级高的线程一直处于等待状态而不是休眠状态,激烈地争夺CPU的资源。
dispatch_semaphore 信号量
GCD中也已经提供了一种信号机制,使用它我们也可以来构建一把”锁”(从本质意义上讲,信号量与锁是有区别)。
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 等待ing");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"线程1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"线程1 发送信号");
NSLog(@"--------------------------------------------------------");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 等待ing");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"线程2");
dispatch_semaphore_signal(signal);
NSLog(@"线程2 发送信号");
});
dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
关于信号量,我们可以用停车来比喻:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值(signal)就相当于剩余车位的数目,dispatch_semaphore_wait 函数就相当于来了一辆车,dispatch_semaphore_signal 就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
2018-01-02 18:47:21.055284+0800 LockTest[6884:391804] 线程1 等待ing
2018-01-02 18:47:21.055288+0800 LockTest[6884:391802] 线程2 等待ing
2018-01-02 18:47:21.055414+0800 LockTest[6884:391802] 线程2
2018-01-02 18:47:21.055501+0800 LockTest[6884:391802] 线程2 发送信号
2018-01-02 18:47:21.055507+0800 LockTest[6884:391804] 线程1
2018-01-02 18:47:21.055576+0800 LockTest[6884:391804] 线程1 发送信号
2018-01-02 18:47:21.056410+0800 LockTest[6884:391804] --------------------------------------------------------
初始信号量大于0
可以发现,因为我们初始化信号量的时候是大于 0 的,所以并没有阻塞线程,而是直接执行了 线程1 线程2。
我们把 信号量初始值改为 0:
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
运行结果:
2018-01-02 18:50:19.370792+0800 LockTest[6971:396072] 线程1 等待ing
2018-01-02 18:50:19.370792+0800 LockTest[6971:396321] 线程2 等待ing
2018-01-02 18:50:29.371276+0800 LockTest[6971:396321] 线程2
2018-01-02 18:50:29.371276+0800 LockTest[6971:396072] 线程1
2018-01-02 18:50:29.371448+0800 LockTest[6971:396321] 线程2 发送信号
2018-01-02 18:50:29.371451+0800 LockTest[6971:396072] 线程1 发送信号
2018-01-02 18:50:29.371584+0800 LockTest[6971:396072] --------------------------------------------------------
互斥锁
synchronized互斥锁性能最差,不推荐使用。
@synchronized(这里添加一个OC对象,一般使用self) {
这里写要加锁的代码
}
注意点
1.加锁的代码尽量少
2.添加的OC对象必须在多个线程中都是同一对象
3.优点是不需要显式的创建锁对象,便可以实现锁的机制。
4. @synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
- (void)synchronizedSaleTickets:(NSString *)flag {
while (1) {
@synchronized(self){
[NSThread sleepForTimeInterval:1];
if (tickets > 0) {
tickets--;
NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票卖完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
}
}
}
pthred_mutex 互斥锁
#import <pthread.h>
- (void)pthread_mutex{
tickets = 20;
// __block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
//1.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self pthread_mutexSaleTickets:@"1"];
});
//1.线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self pthread_mutexSaleTickets:@"2"];
});
}
- (void)pthread_mutexSaleTickets:(NSString *)flag {
while (1) {
[NSThread sleepForTimeInterval:1];
pthread_mutex_lock(&mutex);
if (tickets > 0) {
tickets--;
NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票卖完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
pthread_mutex_unlock(&mutex);
}
}
2018-01-03 14:18:45.837111+0800 LockTest[1680:165345] 剩余票数= 19, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837307+0800 LockTest[1680:165346] 剩余票数= 18, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837481+0800 LockTest[1680:165345] 剩余票数= 17, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837581+0800 LockTest[1680:165346] 剩余票数= 16, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837720+0800 LockTest[1680:165345] 剩余票数= 15, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.837868+0800 LockTest[1680:165346] 剩余票数= 14, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.837978+0800 LockTest[1680:165345] 剩余票数= 13, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.838052+0800 LockTest[1680:165346] 剩余票数= 12, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.838117+0800 LockTest[1680:165345] 剩余票数= 11, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.838352+0800 LockTest[1680:165346] 剩余票数= 10, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.838499+0800 LockTest[1680:165345] 剩余票数= 9, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840398+0800 LockTest[1680:165346] 剩余票数= 8, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840495+0800 LockTest[1680:165345] 剩余票数= 7, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840584+0800 LockTest[1680:165346] 剩余票数= 6, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840694+0800 LockTest[1680:165345] 剩余票数= 5, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.840830+0800 LockTest[1680:165346] 剩余票数= 4, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.840933+0800 LockTest[1680:165345] 剩余票数= 3, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.841050+0800 LockTest[1680:165346] 剩余票数= 2, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.841141+0800 LockTest[1680:165345] 剩余票数= 1, Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
2018-01-03 14:18:45.841310+0800 LockTest[1680:165346] 剩余票数= 0, Thread:<NSThread: 0x60c00026a100>{number = 4, name = (null)}-flag:1
2018-01-03 14:18:45.841386+0800 LockTest[1680:165345] 票卖完了 Thread:<NSThread: 0x604000266c00>{number = 3, name = (null)}-flag:2
NSLock 普通锁
lock、unlock:不多做解释,和上面一样
trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO
- (void)nslock{
tickets = 20;
lock = [[NSLock alloc] init];
//1.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self nslockSaleTickets:@"1"];
});
//1.线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self nslockSaleTickets:@"2"];
});
}
- (void)nslockSaleTickets:(NSString *)flag {
while (1) {
[NSThread sleepForTimeInterval:1];
[lock lock];
if (tickets > 0) {
tickets--;
NSLog(@"剩余票数= %ld, Thread:%@-flag:%@",tickets,[NSThread currentThread],flag);
} else {
NSLog(@"票卖完了 Thread:%@-flag:%@",[NSThread currentThread],flag);
break;
}
[lock unlock];
}
}
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 尝试加速ing...");
BOOL x = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
if (x) {
NSLog(@"线程2");
[lock unlock];
}else{
NSLog(@"失败");
}
});
NSCondition
wait:进入等待状态
waitUntilDate::让一个线程等待一定的时间
signal:唤醒一个等待的线程
broadcast:唤醒所有等待的线程
让一个线程等待2s
NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start");
[cLock lock];
[cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
NSLog(@"线程1");
[cLock unlock];
});
waiting 2秒,唤醒一个等待线程
NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1");
[cLock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
NSLog(@"线程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒一个等待的线程");
[cLock signal];
});
唤醒所有等待的线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒所有等待的线程");
[cLock broadcast];
});
NSRecursiveLock 递归锁
递归锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。
NSLock *rLock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[rLock lock];
if (value > 0) {
NSLog(@"线程%d", value);
RecursiveBlock(value - 1);
}
[rLock unlock];
};
RecursiveBlock(4);
});
2018-01-03 14:40:44.206656+0800 LockTest[1816:185465] 线程4
这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个 block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。
将NSLock 替换为 NSRecursiveLock:
- (void)NSRecursiveLock{
NSRecursiveLock *rLock = [NSRecursiveLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[rLock lock];
if (value > 0) {
NSLog(@"线程%d", value);
RecursiveBlock(value - 1);
}
[rLock unlock];
};
RecursiveBlock(4);
});
}
2018-01-03 14:42:03.128195+0800 LockTest[1852:187555] 线程4
2018-01-03 14:42:03.128399+0800 LockTest[1852:187555] 线程3
2018-01-03 14:42:03.128618+0800 LockTest[1852:187555] 线程2
2018-01-03 14:42:03.128818+0800 LockTest[1852:187555] 线程1
NSConditionLock 条件锁
相比于 NSLock多了个 condition参数,我们可以理解为一个条件标示。
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if([cLock tryLockWhenCondition:0]){
NSLog(@"线程1");
[cLock unlockWithCondition:1];
}else{
NSLog(@"失败");
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"线程2");
[cLock unlockWithCondition:2];
});
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"线程3");
[cLock unlockWithCondition:3];
});
2018-01-03 14:50:58.893535+0800 LockTest[1892:194965] 线程1
2018-01-03 14:50:58.893826+0800 LockTest[1892:194973] 线程3
2018-01-03 14:50:58.894022+0800 LockTest[1892:194966] 线程2
我们在初始化 NSConditionLock 对象时,给了他的标示为 0
执行 tryLockWhenCondition:时,我们传入的条件标示也是 0,所 以线程1 加锁成功
执行 unlockWithCondition:时,这时候会把condition由 0 修改为 1
因为condition 修改为了 1, 会先走到 线程3,然后 线程3 又将 condition 修改为 3
最后 走了 线程2 的流程