如果我们在并发队列中造成死锁会怎样?

What happens if we cause a deadlock in a concurrent queue?

我在 apple 文档中看到了这个:

重要提示:永远不要从计划传递给函数的同一队列中执行的任务调用 dispatch_sync 或 dispatch_sync_f 函数。这对于串行队列尤为重要,它保证死锁,但对于并发队列也应该避免。

第一种情况导致崩溃,但第二种情况不会导致应用程序运行。在并发队列执行的任务中调用 sync 到底发生了什么?

let concurrentQueue = DispatchQueue(label: "queue2.concurrent", attributes: .concurrent)

    for _ in 0..<10 {
        concurrentQueue.async {
            print(Thread.current)
            concurrentQueue.sync {
                print(Thread.current)
                print(2)
            }
        }
    }

是否创建了很多线程?我没有在调试器中看到它。而且应用程序不会崩溃。

您提供的代码片段不会导致死锁。

for 循环会将 10 个任务放入队列,然后执行将从代码段的底部开始。队列将在资源允许的情况下在 GCD 线程池中的线程上启动这些任务,如果可用 CPU 时间多于现有线程可以利用的时间,则可能会创建新线程。每个任务都会打印自己当前的线程描述,然后同步提交内部任务。

同步提交内部任务只是意味着外部任务不会继续(直到结束)直到内部任务完成。

现在,理论上,队列可以 运行 GCD 池中另一个线程上的内部任务。然而,由于 GCD 知道调用线程被其他方式阻塞,它实际上 运行 内部任务就在调用线程上。因此,总有一个线程可用,没有什么能阻止内部任务立即开始。

因此,如果内部任务简单地内联到外部任务中,您的代码的行为非常相似:

for _ in 0..<10 {
    concurrentQueue.async {
        print(Thread.current)
        print(Thread.current)
        print(2)
    }
}

以上忽略障碍任务。这些可以阻止并发队列执行其他任务,即使有可用线程也是如此。换句话说,它有点使并发队列暂时串行。那样的话,肯定是有可能死锁的。

在任何情况下,如果提交到并发队列的任务发生死锁(以任何方式,不仅仅是通过同步将任务提交到同一队列;例如,通过等待永远不会发出信号的信号量)那么该线程将永远被阻塞,无法继续。如果任务不是屏障或没有被屏障任务同步等待,则队列不会被阻塞。它可以继续启动已在其上排队的其他(非障碍)任务。

卡住的线程会消耗固定数量的内存和一些内核簿记数据。但它不会消耗 CPU。但是,GCD 对其启动的线程数有内部限制。如果你阻止足够多的它们,它最终将停止执行任何异步任务。