1

假设我们有一个共享资源,一堆不同的全局队列可以访问,为了这个问题,我们使用 Dispatch Semaphore 来管理该访问。当这些全局队列之一告诉信号量等待时,信号量计数会减少,并且该线程可以访问共享资源。是否有可能在信号量等待时,另一个(不同的)全局队列尝试访问此共享资源,而 GCD 从其池中抓取的线程与为前一个队列(当前正在制作的队列)抓取的线程相同信号量等待)这会死锁这个线程并防止信号量计数重新增加?

4

1 回答 1

5

简短的回答:

是的,使用信号量可能会导致死锁,但不是出于您建议的原因。

长答案:

如果您有一些已调度的任务在等待信号量,则该工作线程将被阻塞,直到收到信号并恢复执行并随后返回。因此,您不必担心另一个分派的任务会尝试使用同一个线程,因为该线程会暂时从线程池中删除。您永远不必担心尝试同时使用同一个线程的两个分派任务。这不是死锁风险。

话虽如此,我们必须对线程池中工作线程的数量极为有限(目前每个 QoS 64 个)这一事实保持敏感。如果你用尽了可用的工作线程,那么任何其他分派到 GCD(具有相同的 QoS)的东西都无法运行,直到一些以前阻塞的工作线程再次可用。

考虑:

print("start")

let semaphore = DispatchSemaphore(value: 0)
let queue = DispatchQueue.global()
let group = DispatchGroup()
let count = 10

for _ in 0 ..< count {
    queue.async(group: group) {
        semaphore.wait()
    }
}

for _ in 0 ..< count {
    queue.async(group: group) {
        semaphore.signal()
    }
}

group.notify(queue: .main) {
    print("done")
}

这很好用。你有十个工作线程与这些wait调用绑定,然后另外十个调度的块调用signal,你很好。

但是,如果增加到count100(称为“线程爆炸”的情况),上述代码将永远无法自行解决,因为signal调用正在等待与所有这些wait调用绑定的工作线程。那些带有调用的分派任务signal都没有机会运行。而且,当您耗尽工作线程时,这通常是一个灾难性问题,因为任何尝试使用 GCD(对于相同的 QoS)的东西都将无法运行。


顺便说一句,在线程爆炸场景中使用信号量只是导致死锁的一种特殊方式。但为了完整起见,值得注意的是,信号量有很多死锁的方法。最常见的例子是信号量(或调度组或其他)用于等待一些异步进程,例如

let semaphore = DispatchSemaphore(value: 0)
someAsynchronousMethod {
    // do something useful

    semaphore.signal()
}
semaphore.wait()

如果(a)您从主队列运行它,那可能会死锁;但是 (b) 异步方法也碰巧在主队列上调用了它的完成处理程序。这是典型的信号量死锁。

我只使用了上面的线程爆炸示例,因为死锁并不完全明显。但显然有很多方法可以导致信号量死锁。

于 2020-05-28T05:28:33.733 回答