iOS-GCD多线程死锁案例
一、死锁场景: 主线程调用主线程。
- (void)deadLockCase1 {
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
}
控制台输出:1 ,后面就崩溃了。
原因:
从控制台输出可以看出,任务2和任务3没有执行,此时已经死锁了。
因为dispatch_sync是同步的,本身就会阻塞当前线程,此刻阻塞了主线程。而当前block又在等待主线程执行完毕,从而形成了主线程等待主线程,自己等自己的情况,形成了死锁。
解决方法:
1、改用异步dispatch_async执行
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
控制台输出:1 3 2
2、不在主线程中运行,而是放在子线程中
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
控制台输出:1 2 3
注:如果block中是刷新UI的操作,则不能放在子线程中执行,会crash
二、死锁场景2: (同步串行队列嵌套自己)
- (void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); //任务1
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"2"); //任务2
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"3"); //任务3
});
NSLog(@"4"); //任务4
});
NSLog(@"5"); //任务5
}
控制台输出:1 2 ,执行到2后面就崩溃了。
原因:
从控制台输出结果来看,执行到任务2后,就已经死锁了。因为该例子中两个GCD都是使用的同步方式,而且还是同一个串行队列,这就导致了和上一个例子一样,自己在等待自己的情况,形成了死锁。
解决方法:
1、将第二个GCD改为异步
- (void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); //任务1
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"2"); //任务2
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"3"); //任务3
});
NSLog(@"4"); //任务4
});
NSLog(@"5"); //任务5
}
控制台输出:1 2 4 5 3
然而,将第一个GCD改为异步,不能解决问题
- (void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); //任务1
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"2"); //任务2
dispatch_sync(aSerialDispatchQueue, ^{
NSLog(@"3"); //任务3
});
NSLog(@"4"); //任务4
});
NSLog(@"5"); //任务5
}
控制台输出:
1
5
2
原因:
虽然第一个GCD是异步的,但是第二个GCD是同步的,第二个GCD在等着第一个GCD结束,而第一个GCD的block又在等着第一个GCD结束,这样就形成了死锁。
注:对于以上将第二个GCD改为异步,第一个GCD为同步的场景,不会造成死锁,是因为第二个GCD为异步,它不用等待第一个GCD执行完毕,它和第一个GCD是没有同步关系的。它是在第一个GCD执行的同时并发执行自己block的代码。
2、将两个GCD都改为异步
- (void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); //任务1
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"2"); //任务2
dispatch_async(aSerialDispatchQueue, ^{
NSLog(@"3"); //任务3
});
NSLog(@"4"); //任务4
});
NSLog(@"5"); //任务5
}
控制台输出:
1
5
2
4
3
3、使用不同的串行队列
- (void)deadLockCase2 {
dispatch_queue_t aSerialDispatchQueue1 = dispatch_queue_create("com.test.deadlock.queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t aSerialDispatchQueue2 = dispatch_queue_create("com.test.deadlock.queue2", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); //任务1
dispatch_sync(aSerialDispatchQueue1, ^{
NSLog(@"2"); //任务2
dispatch_sync(aSerialDispatchQueue2, ^{
NSLog(@"3"); //任务3
});
NSLog(@"4"); //任务4
});
NSLog(@"5"); //任务5
}
控制台输出:
1
2
3
4
5
三、 死锁场景: 信号量阻塞主线程
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSLog(@"semaphore create!");
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semaphore);
NSLog(@"semaphore plus 1");
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore minus 1");
}
原因:
如果当前执行的线程是主线程,以上代码就会出现死锁。
因为dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)阻塞了当前线程,而且等待时间是DISPATCH_TIME_FOREVER——永远等待,这样它就永远的阻塞了当前线程——主线程。导致主线中的dispatch_semaphore_signal(semaphore)没有执行,
而dispatch_semaphore_wait一直在等待dispatch_semaphore_signal改变信号量,这样就形成了死锁。
解决方法:
应该将信号量移到并行队列中,如全局调度队列。以下场景,移到串行队列也是可以的。但是串行队列还是有可能死锁的(如果执行dispatch_semaphore_signal方法还是在对应串行队列中的话,即之前提到的串行队列嵌套串行队列的场景)。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSLog(@"semaphore create!");
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semaphore);
NSLog(@"semaphore plus 1");
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore minus 1");
});
}
上面从上到下顺序输出
以下代码运行结果如何?
-(void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
只输出:1。后面就崩溃了(主线程死锁,因为viewDidLoad方法默认开了一条主线程,然后又执行dispatch_sync(dispatch_get_main_queue(), ^{...});会导致你等我我等你,结果导致死锁。