说说GCD中的死锁
本文主要举例说明GCD里的死锁场景,分析造成死锁的原因以及解决方案
在开始说GCD死锁之前,我们先了解一下GCD的中的任务派发和队列。
任务派发
任务派发方式 | 说明 |
---|---|
dispatch_sync() | 同步执行,完成了它预定的任务后才返回,阻塞当前线程 |
dispatch_async() | 异步执行,会立即返回,预定的任务会完成但不会等它完成,不阻塞当前线程 |
队列种类
队列种类 | 说明 |
---|---|
串行队列 | 每次只能执行一个任务,并且必须等待前一个执行任务完成 |
并发队列 | 一次可以并发执行多个任务,不必等待执行中的任务完成 |
GCD队列种类
GCD队列种类 | 获取方法 | 队列类型 | 说明 |
---|---|---|---|
主队列 | dispatch_get_main_queue | 串行队列 | 主线中执行 |
全局队列 | dispatch_get_global_queue | 并发队列 | 子线程中执行 |
用户队列 | dispatch_queue_create | 串并都可以 | 子线程中执行 |
GCD死锁
在GCD中,主要的死锁就是当前串行队列里面同步执行当前串行队列。解决的方法就是将同步的串行队列放到另外一个线程执行。
死锁场景
1. 死锁场景: 主线程调用主线程
- (void)deadLockCase1 {
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
}
控制台输出:
1
原因:
从控制台输出可以看出,任务2和任务3没有执行,此时已经死锁了。
因为dispatch_sync是同步的,本身就会阻塞当前线程,此刻阻塞了主线程。而当前block又在等待主线程执行完毕,从而形成了主线程等待主线程,自己等自己的情况,形成了死锁。
解决方法:
- 改用异步dispatch_async执行
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
控制台输出:
1
3
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后,就已经死锁了。因为该例子中两个GCD都是使用的同步方式,而且还是同一个串行队列,这就导致了和上一个例子一样,自己在等待自己的情况,形成了死锁。
解决方法:
- 将第二个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的代码。
- 将两个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
- 使用不同的串行队列
- (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
3. 死锁场景: 信号量阻塞主线程
- (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");
});
}