Ios 关于调度队列、重入和死锁的澄清

Ios 关于调度队列、重入和死锁的澄清,ios,multithreading,macos,grand-central-dispatch,deadlock,Ios,Multithreading,Macos,Grand Central Dispatch,Deadlock,我需要澄清一下dispatch\u queues与重入和死锁的关系 读到这篇博文,我遇到了这样一句话: 所有调度队列都是不可重入的,这意味着如果 您试图在当前队列上调度\u sync 那么,重入和死锁之间的关系是什么?为什么,如果dispatch\u队列不可重入,那么在使用dispatch\u sync调用时会出现死锁 根据我的理解,只有在运行的线程与将块分派到的线程相同时,才能使用dispatch\u sync产生死锁 下面是一个简单的例子。如果我在主线程中运行代码,因为dispatch\u

我需要澄清一下
dispatch\u queue
s与重入和死锁的关系

读到这篇博文,我遇到了这样一句话:

所有调度队列都是不可重入的,这意味着如果 您试图在当前队列上调度\u sync

那么,重入和死锁之间的关系是什么?为什么,如果
dispatch\u队列
不可重入,那么在使用
dispatch\u sync
调用时会出现死锁

根据我的理解,只有在运行的线程与将块分派到的线程相同时,才能使用
dispatch\u sync
产生死锁

下面是一个简单的例子。如果我在主线程中运行代码,因为
dispatch\u get\u main\u queue()
也将抓住主线程,我将以死锁结束

dispatch_sync(dispatch_get_main_queue(), ^{

    NSLog(@"Deadlock!!!");

});
有什么澄清吗

所有调度队列都是不可重入的,这意味着如果 您试图在当前队列上调度\u sync

那么,重入和死锁之间的关系是什么?为什么,如果 dispatch_队列是不可重入的,在重入时是否会出现死锁 使用调度同步呼叫

在没有读过那篇文章的情况下,我猜想该语句是针对串行队列的,因为它在其他方面是错误的

现在,让我们考虑一个简简单单的概念:调度队列是如何工作的(在一些伪语言中)。我们也假设了一个串行队列,并且不考虑目标队列。 调度队列 当您创建一个调度队列时,基本上您会得到一个FIFO队列,一个简单的数据结构,您可以将对象推到末端,并将对象从前端移除

您还可以使用一些复杂的机制来管理线程池和执行同步,但其中大部分是为了提高性能。让我们简单地假设您还得到了一个线程,它只运行一个无限循环,处理来自队列的消息

void processQueue(queue) {
    for (;;) {
        waitUntilQueueIsNotEmptyInAThreadSaveManner(queue)
        block = removeFirstObject(queue);
        block();
    }
}
异步调度 采用同样简单的
dispatch\u async
视图会产生如下结果

void dispatch_async(queue, block) {
    appendToEndInAThreadSafeManner(queue, block);
}
它真正要做的就是获取块,并将其添加到队列中。这就是它立即返回的原因,它只是将块添加到数据结构的末尾。在某个时刻,另一个线程将把这个块从队列中拉出来,并执行它

请注意,这是FIFO保证发挥作用的地方。从队列中拉出块并执行它们的线程总是按照它们在队列中的放置顺序将它们带走。然后等待该块完全执行,然后将下一个块移出队列

调度同步 现在,另一个简单化的
调度同步视图
。在这种情况下,API保证它将等到块运行完成后再返回。特别是,调用此函数不会违反FIFO保证

void dispatch_sync(queue, block) {
    bool done = false;
    dispatch_async(queue, { block(); done = true; });
    while (!done) { }
}
现在,这实际上是用信号量完成的,所以没有cpu循环和布尔标志,也不使用单独的块,但我们试图保持简单。你应该明白这一点

块被放置在队列上,然后函数等待,直到它确定“另一个线程”已经运行块完成为止

可重入 现在,我们可以通过多种不同的方式获得可重入调用。让我们考虑最明显的。

block1 = {
    dispatch_sync(queue, block2);
}
dispatch_sync(queue, block1);
这将在队列上放置block1,并等待它运行。最终,处理队列的线程将弹出block1,并开始执行它。当block1执行时,它会将block2放在队列上,然后等待它完成执行

这是重入的一种含义:当您从另一个调用重新输入调用
dispatch\u sync
dispatch\u sync

重新进入时出现死锁
dispatch\u sync
但是,block1现在正在队列的for循环中运行。该代码正在执行block1,在block1完成之前,不会再处理队列中的任何内容

但是,Block1已将block2放在队列上,并正在等待它完成。Block2确实已放置在队列上,但它永远不会执行。Block1正在“等待”block2完成,但block2位于队列上,在Block1完成之前,将其从队列中拉出并执行它的代码不会运行

无法重新进入的死锁
dispatch\u sync
现在,如果我们把代码改成这个

block1 = {
    dispatch_sync(queue, block2);
}
dispatch_async(queue, block1);
从技术上讲,我们没有重新进入
调度同步
。然而,我们仍然有相同的场景,只是启动block1的线程没有等待它完成

我们仍在运行block1,等待block2完成,但将运行block2的线程必须首先使用block1完成。这永远不会发生,因为处理block1的代码正在等待block2从队列中取出并执行

因此,调度队列的可重入性在技术上不是重新进入相同的函数,而是重新进入相同的队列处理

完全不重新进入队列的死锁 在最简单的情况下(也是最常见的),让我们假设
[self-foo]
在主线程上被调用,这对于UI回调来说很常见

-(void) foo {
    dispatch_sync(dispatch_get_main_queue(), ^{
        // Never gets here
    });
}
这不会“重新输入”调度队列API,但具有相同的效果。我们在主线程上运行。主线程是从主队列中取出块并进行处理的位置。主线程当前正在执行
foo
,一个块被放置在主队列上,然后
foo
等待执行该块。但是,它只能在主线程完成当前工作后从队列中取出并执行

这永远不会发生,因为主线程在`foo完成之前不会进行,但在等待运行的块之前它永远不会完成。。。这不会发生

据我所知,您只能使用dispatch_sync出现死锁 如果