【问题标题】:why concurrentQueue.sync DON'T cause deadlock为什么 concurrentQueue.sync 不会导致死锁
【发布时间】:2018-11-14 05:56:15
【问题描述】:

这段代码会死锁,因为:

  1. 他们在同一个线程中
  2. print(2) 必须等待 print(3)
  3. print(3) 必须等待 print(2)

例如:

DispatchQueue.main.async {
    print(Thread.current)
    DispatchQueue.main.sync {
        print(Thread.current)
        print(2)
    }
    print(3)
}

为什么在concurrentQueue 不会造成死锁?它们也在同一个线程中。

DispatchQueue.global().async {
    print(Thread.current)
    DispatchQueue.global().sync {
        print(Thread.current)
        print(2)
    }
    print(3)
}

【问题讨论】:

    标签: ios concurrency grand-central-dispatch deadlock


    【解决方案1】:

    你问:

    为什么在concurrentQueue 不会造成死锁?它们也在同一个线程中。

    不,它们必须在同一个线程中。它们在同一个队列,,但不一定是同一个线程。(作为优化的一部分,见下文,它实际上可能最终在同一个线程上运行,但不一定如此。但从逻辑上讲,您应该将其视为在单独的线程上运行。)

    这就是“并发队列”背后的全部想法,它们可以在不同的线程上运行单独的分派任务。这就是它们允许并发操作的方式。并发队列上的一个分派任务可以在一个线程上运行,而同一队列上的另一个分派任务可以在单独的线程上运行。

    正如旧的Concurrency Programming Guide 在“并发队列”的定义中所说:

    当前正在执行的任务在由调度队列管理的不同线程上运行。

    或者,作为DispatchQueue documentation says

    DispatchQueue 管理工作项的执行。提交到队列的每个工作项都在系统管理的线程池上进行处理。 [强调]


    让这更令人困惑的是有一个 GCD 优化,如果你是 dispatching synchronously ...

    作为性能优化,这个函数尽可能在当前线程上执行块...

    因此,当从队列同步调度时,它实际上可以最终在同一个线程上运行该代码(除非您从后台队列调度到主队列)。考虑:

    let queue = DispatchQueue.global()
    let queue2 = DispatchQueue(label: "queue2")
    
    queue.async {
        print(1, Thread.current)
        queue2.sync {
            print(2, Thread.current)
        }
        print(3, Thread.current)
    }
    

    第二个print 语句将表明,即使您有一个完全不同的队列,作为上述sync 优化的一部分,它也可以在当前线程上运行代码。如果内部 sync 调用与外部块被分派到的 queue 相同,情况也是如此。

    现在看这个优化的结果,感觉和串行队列的场景很像,其实不然。串行队列一次只允许一个调度的任务运行,因此根据定义,尝试同步调度(阻塞当前线程直到调度的块运行)到它自己是死锁。

    但是并发队列分派给自己通常不会死锁。一个警告是工作线程的数量是相当有限的,所以如果你有“线程爆炸”(你可能有超过 64 个工作线程在运行),那么它可能会死锁,所以请确保将你的并发限制在合理的范围内价值。

    【讨论】:

    • 正确。 sync 方法将作为优化,如果可以的话,在当前线程上运行代码。我试图澄清我上面的答案。
    • 这是否意味着OP提到的第二个示例是一次又一次地创建死锁,并且正因为如此,正在发生线程爆炸?谢谢。
    • @Ricardo - 不,在 OP 的第二个示例中,它是一个全局队列,它是一个并发队列,因此它不会死锁(除非工作线程已经耗尽,这不是案例在这里)。如果您在串行队列上使用此模式,您只会死锁。而且,不,这不符合“线程爆炸”的条件,而是他的并发队列模式将使用两个工作线程,这很快就会得到缓解。如果您冒着耗尽 64 个工作线程池的风险,您通常只会将其视为“爆炸”,这在此处不是实际风险。
    猜你喜欢
    • 2015-03-09
    • 2019-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-20
    • 1970-01-01
    • 2012-08-27
    相关资源
    最近更新 更多