关于GCD死锁

文章首发于个人博客地址:关于GCD死锁
如需转载,请附带说明文章出处。

问题

有很多文章经常会说“在主线程使用了sync函数就会造成死锁”或者是“在主线程使用了sync函数,同时将任务传入串行队列就会死锁”。那么这些说法是否正确呢?
答:不正确!! 。 GCD死锁的原因是队列阻塞而不是线程阻塞。 那么GCD死锁到底是怎么回事儿呢?

本文的分析基于已经了解了GCD的基本知识。

基本知识介绍

串行队列和并行队列

参照下图:

串行和并行队列

上图表明了几点:

  • 串行队列和并行队列都是先进先出,区别在于其队列中任务的执行方式。
  • 串行队列中,下一个任务会等待上一个任务结束后才会执行。
  • 并行队列中,不会等待上一个任务完全执行结束,就会立即调用执行下一个任务。
同步函数和异步函数
dispatch_sync函数:
  • 意味着 ”同步“,将指定的block”同步“的追加到指定的 dispatch_queue 中。
  • 再追加的block执行结束之前, dispatch_sync 函数会一直等待。
  • ”等待“ 也就意味着当前线程停止。如下图所示:

dispatch_sync

dispatch_async函数:
  • 意味着”非同步“,将指定的block非同步的添加到指定的 dispatch_queue 中。
  • dispatch_async 不会等待追加的block执行结束。如下图所示:

dispatch_async

经典GCD死锁案例和解析

GCD主队列死锁

经典案例如下图所示:

image.png

我们发现仅仅执行打印了“开始执行”,然后报错,代码中使用了sync函数往主队列中追加了一个block,然后在主线程中执行。

解析

上述代码中,表示的主队列中任务情况如下图所示:

image.png

按照主线程中执行的顺序进行梳理:

  1. 主线程首先从主队列中提取任务viewDidLoad,并开始执行。
  2. 代码执行第一行,打印“开始执行–main thread”。
  3. 代码执行到第二行,使用 dispatch_sync 函数同步的往主队列中添加一个block,此时主队列中有了两个任务。
  4. 基于 dispatch_sync 的特点,此时该同步函数处在等待状态,等待它添加的block处理执行完毕。
  5. 程序出现崩溃。

并没有打印block中的内容,那么也就是 block没有执行。 为什么不会执行呢?

  1. dispatch_sync 在它追加进指定队列的block处理执行结束前处于等待状态。
  2. 此时 dispatch_sync 函数将block任务追加进了主队列中,主队列是串行队列,那么此时主队列中有了两个任务viewDidLoad和追加的block任务。
  3. 串行队列中block任务要执行的话,那么就需要等待它前面的viewDidLoad任务执行结束。
  4. viewDidLoad任务中,此时 dispatch_sync 函数处于等待状态,需要等到 dispatch_sync 函数返回后才能继续往下执行。
  5. 由此就形成了一个死锁。
    • viewDidLoad等待 dispatch_sync 函数返回。
    • dispatch_sync 函数等待block执行结束。
    • block任务等待viewDidLoad执行结束

如下图所示:

image.png

死锁解决方案

从上面的分析中,我们可以得出造成死锁的关键点:

  1. dispatch_sync 函数阻塞了当前线程
  2. 串行队列中两个任务形成队列阻塞。
针对sync函数阻塞了当前线程

dispatch_sync 函数”同步“的添加block任务到队列,阻塞线程。那么我们使用 dispatch_async 函数”非同步“的将block任务添加进队列,不阻塞线程就可以解决。
如下图所示:

image.png

可以发现没有报错,打印顺序为”开始执行“->”结束执行“->“执行中”。

解析

按照执行流程进行梳理:

  1. 主线程从主队列中取出 viewDidLoad 任务开始执行。
  2. 打印”开始执行–main thread“。
  3. dispatch_async 异步将block任务追加到主队列中,并且不会等待 block 任务执行结束。
  4. 打印”结束执行–main thread“。
  5. 此时追加进主队列中的 block 任务因为 viewDidLoad 任务结束,开始执行并打印”执行中–main thread“。
针对串行队列中的队列阻塞

上述造成死锁的主队列中, viewDidLoad 因为 dispatch_sync 函数处于等待状态而不能执行结束,而 block 又需要等待 viewDidLoad 执行结束。那么有几种方法可以解决:

  1. 自行创建一个队列,将 block 追加新建的队列中,这样两个任务就不会造成队列阻塞了。
  2. 将多个任务添加进并发队列中,这样任务执行就不用等待上一个任务执行结束了。
新建队列

如下图所示:
image.png

创建了一个自定义串行队列”com.wjsuumer.top.test"(以下简称test队列)。然后使用 dispatch_sync 函数将 block 任务添加进该队列。运行后控制台依次打印 “开始执行”->“执行中”->“结束执行”。

解析

对于自定义的串行队列,添加 block 任务后,那么此时两个队列的情况如下图所示:

image.png

按照执行流程进行梳理:

  • 主线程从主队列取出任务 viewDidLoad 开始执行。
  • 执行第一行,打印”开始执行–main thread“。
  • 执行第二行,创建了一个test队列。
  • 执行第三行,使用 dispatch_sync 函数往test队列中添加一个 block 任务,此时 dispatch_sync 处于等待状态。
  • 主线程从test队列中取出 block 任务执行,打印”执行中–main thread“。
  • 此时 block 执行结束, dispatch_sync 函数返回,继续往下执行。
  • 执行最后一行代码,打印”结束执行–main thread“。

相比之前直接在主队列中添加 block 任务,从test队列中取出 block 任务时,因为 block 任务就在test队列的对头,不需要等待其他任务就可以立即调用。

注意:这个例子中我们只需要追加一个block任务,所以创建的队列无论是串行队列还是并行队列都可以,因为都是将block添加进了一个新的队列中。

使用并发队列

对于上述的新建队列的方法,如果新建的是串行队列,那么针对下列情况结果会是怎样的呢?

image.png

运行会发现依然会发生崩溃,自建test队列中发生死锁,test队列中有 block1 任务和 block2 任务,因为test队列为串行队列,所以 block2 任务需要等待 block1 任务执行完毕后才能执行,但是 block1 任务中dispatch_sync因为 block2 没有执行结束而处于等待状态,从而造成死锁。
如下图所示:

image.png

那么此时可能会想到针对 block2 任务,再新建一个队列,然后将 block2 追加到新的队列中去,这样就不会造成死锁了。这样自然可以,但是使用并发队列更加的简单。
使用并发队列如下图所示:

image.png

解析

我们将 block1 block2 追加进了新建的全局并发队列中,由于并发队列中的任务并发执行,不需要等待上一个任务执行结束,也就不会出现死锁的情况。

练习案例分析

如下代码所示,是否会发生死锁的情况?为什么?如果死锁了应该如何解决?

- (void)viewDidLoad {
    
    NSLog(@"开始执行--%@",[NSThread currentThread]);
    //使用async往主队列中添加block1任务
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"执行中--1--%@",[NSThread currentThread]);
        //使用sync函数往主队列中添加block2任务。
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"执行中--2--%@",[NSThread currentThread]);
        });
    });
    NSLog(@"结束执行--%@",[NSThread currentThread]);
}

运行这段代码会出现崩溃,其中发生了死锁。

image.png

按照执行顺序进行梳理:

  1. 主线程从主队列中取出 viewDidLoad 任务开始执行,打印**“开始执行–main thread”**。
  2. dispatch_async 函数异步将 block1 任务添加进主队列,同时不会处于等待状态。
  3. 打印**“结束执行–main thread”**。
  4. 此时 viewDidLoad 执行结束,开始执行添加的 block1 任务,打印**“执行中–1--main thread”**。
  5. block1 中使用 dispatch_sync 函数添加 block2 任务到主队列中,并且处于等待状态,等待 block2 任务执行完毕。
  6. block2 在主队列中,那么需要等待它前面的 block1 任务执行完毕。此时形成了死锁,程序崩溃。

为了解决这样的死锁问题,可以使用 dispatch_sync 函数“非同步”来追加 block2 任务。

结语

这篇笔记中,主要是讲解了 dispatch_sync 函数和diapatch_async将任务追加到串行队列中引发的死锁问题以及如何去解决。
其中没有讲述过使用 dispatch_async +并行队列的形式,这种情况下会开启子线程对任务进行处理,关于GCD多线程编程在其他笔记中详述。
对于GCD的使用,还是需要理解其原理机制,才能够更好的配合实现性能的优化,避免出现死锁之类的错误。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值