文章目录
  1. 1. 起因
  2. 2. GCD导致死锁的例子
  3. 3. 小结

起因


所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

例如:

死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

GCD导致死锁的例子


比如下面的这个会产生死锁的例子:

1
2
3
4
5
6
7
8
9
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}

你可以自己先分析一下该程序产生死锁的原因。

我们首先要搞清楚同步&异步 串行&并发这两组基本概念:

同步执行:比如dispatch_sync,这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的。

异步执行:一般使用dispatch_async,这个函数也会把一个block加入到指定的队列中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。

接下来是”串行和并行”:

串行队列:比如dispatch_get_main_queue,这个队列中所有任务,一定按照先来后到的顺序执行。不仅如此,还可以保证在执行某个任务时,在它前面进入队列的所有任务肯定执行完了。对于每一个不同的串行队列,系统会为这个队列建立唯一的线程来执行代码。

并发队列:比如dispatch_get_global_queue,这个队列中的任务也是按照先来后到的顺序开始执行,注意是开始,但是它们的执行结束时间是不确定的,取决于每个任务的耗时。对于n个并发队列,GCD不会创建对应的n个线程而是进行适当的优化。

理解清楚上面的两组概念后我们再来分析上面例子产生死锁的原因:

该程序中在主线程中同步执行dispatch_sync(queue,block)任务,而该任务又被分发到了主线程dispatch_get_main_queue()中,这就产生了问题。程序在执行dispatch_sync(queue,block)时,会阻塞调用线程(在这里是主线程),等待block中的任务执行完后再返回阻塞线程继续进行执行,但是block中的任务在执行的时候又需要在主线程中执行,而现在主线程被阻塞,所以就会产生两者相互等待的情况,死锁就形成了。

下面对比着总结一下dispatch_syncdispatch_async的执行流程:

(1)dispatch_sync的执行流程:

  • 将block添加到queue队列中
  • 阻塞调用线程,等待block()执行结束,回调到调用线程。

(2)dispatch_async的执行流程:

  • 将block添加到queue队列中
  • 直接回到调用线程(不阻塞调用线程),异步执行。

小结


下面我总结一下关于死锁的一些注意事项:

  • 异步执行一定不会产生死锁,因为异步的执行,block会立刻返回,不会阻塞线程。所以在我们开发过程中如果遇到死锁的问题,很大可能是同步执行的原因,这样可以帮助我们快速定位。
  • 不要把block任务同步派发到调用gcd所在线程的关联队列中。
文章目录
  1. 1. 起因
  2. 2. GCD导致死锁的例子
  3. 3. 小结