iOS多线程之二:GCD死锁与堵塞

322 阅读4分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

前言

上一篇,我们写了关于iOS多线程之一:进程,线程,队列的关系,描写了进程线程的关系,线程队列的关系,还有关于异步同步串行并发的简单使用。那这篇文章,我们就接着上一篇的内容,分析一下关于死锁,以及堵塞

死锁

死锁简单来说是指2个任务之间互相等待,任务A需要等待任务B执行完后才执行,而任务B也需要等待任务B执行完后才执行,就会造成线程死锁

同步主队列死锁

下面我们先来描述一下主队列死锁的问题,对于这种情况死锁相信面试过的同事,应该都有遇到过。

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_sync(dispatch_get_main_queue(), ^{

        NSLog(@"任务A");

    });

    NSLog(@"任务B");

}

运行后:没有打印任务B,并且在dispatch_sync行报错。

我们先来简单分析一下:

  • viewDidLoad: 这个方法是在主线程运行的,主线程又是执行主队列的任务的。

  • dispatch_sync:是同步线程,有这个就不用想了,必造成堵塞。堵塞就意味着必须先执行完同步线程的任务,才能结束Block,继续往下面走。

  • 任务B:任务B也是主队列的任务。前面堵塞了,必须等走完任务A,才能轮到任务B。

可是问题出现了啊,出现在哪呢,就出现在任务A。任务A它也是加入主队列的任务。

坏了坏了,主队列是遵循先进先出原则,也就是我们说的FIFO原理。它必须放在主队列的最后面。也就是说他必须等任务B执行完后,才能轮到它。

苍天饶过谁,任务A在等任务B完成,任务B也在等任务A完成,GG,直接线程死锁。

那如何破解呢?

有3个方法。

  1. dispatch_sync同步线程,改成dispatch_sync异步线程。
  2. dispatch_syn的同步线程,任务A就不要加入主队列,可以加入其它队列(串行,并发)都行。
  3. 全部干掉,不做就不会错(开玩笑的)。

同步串行队列死锁

串行队列造成死锁的情况有遇到过吗,下面来个例子看看。

- (void)demo
{

    dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{

        NSLog(@"任务1:%@",[NSThread currentThread]);

        dispatch_sync(queue, ^{

            NSLog(@"任务2:%@",[NSThread currentThread]);

        });

        NSLog(@"任务3:%@",[NSThread currentThread]);

    });

    NSLog(@"主线程执行");

}

先看下结果,的确是出现死锁了。

Snipaste_2022-02-15_22-15-14.png

先来分析一波:

  • dispatch_async:先来一个异步线程,而且是异步串行,这种情况会创建新的线程,所以我们能看到打印任务1是个子线程。

  • 主线程执行:这里的打印是先走的,没有问题,异步线程是不会影响到主线程执行任务的。

  • dispatch_sync:又是同步线程,这家伙又堵塞住当前的线程,看截图,我们可以叫线程6。堵塞了线程6啊。

  • 任务3:任务3是在串行队列中的,签名的同步线程堵塞住了,所以任务3需要等待同步线程执行完,线程6才会执行任务3。

  • 任务2:任务2是加入到串行队列的,是同一个串行队列,所以它啊,按照FIFO原则,需要排在任务3后面,也就是说任务3执行完才能执行任务2。

该来的还是来了,任务3必须等同步线程执行完任务2,任务2在串行队列中必须排再任务3后面,又一个互等,结果无疑是死锁啦。

是不是和第一个例子类似,主队列也是一个特殊的串行队列,所以效果也是一样的。

解决方案:

在同步线程里面,想用串行队列也可以,只要不是同一个即可。

- (void)demo
{

    dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{

        NSLog(@"任务1:%@",[NSThread currentThread]);

        dispatch_sync(dispatch_queue_create("jj2.com", DISPATCH_QUEUE_SERIAL), ^{

            NSLog(@"任务2:%@",[NSThread currentThread]);

        });

        NSLog(@"任务3:%@",[NSThread currentThread]);

    });

    NSLog(@"主线程执行");

}

Snipaste_2022-02-15_22-28-34.png

原因还要说吗,说吧,不然怕到时候自己重新看时没记起来。说白了就是这是2个不同的串行队列,串行队列1的任务1执行后,同步线程堵塞了,先把串行队列2的任务交给子线程执行,执行后,串行队列1的任务3再执行。

所以就没有出现死锁的情况,只因为这是2个不同的队列,任务2不用等任务3执行了。

总结

在当前串行队列中,使用sync同步函数,往当前串行队列中添加任务,会卡在当前的串行队列,从而造成死锁

参考文章: