调度信号量是否会无意中使自身陷入死锁?

hga*_*ale 1 grand-central-dispatch ios dispatchsemaphore

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

Rob*_*Rob 5

简短回答:

\n\n

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

\n\n

长答案:

\n\n

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

\n\n

话虽如此,我们必须意识到线程池中工作线程的数量极其有限(目前每个 QoS 64 个)。如果耗尽可用的工作线程,则调度到 GCD(具有相同 QoS)的其他任何内容都无法运行,直到某些先前阻塞的工作线程再次可用。

\n\n

考虑:

\n\n
print("start")\n\nlet semaphore = DispatchSemaphore(value: 0)\nlet queue = DispatchQueue.global()\nlet group = DispatchGroup()\nlet count = 10\n\nfor _ in 0 ..< count {\n    queue.async(group: group) {\n        semaphore.wait()\n    }\n}\n\nfor _ in 0 ..< count {\n    queue.async(group: group) {\n        semaphore.signal()\n    }\n}\n\ngroup.notify(queue: .main) {\n    print("done")\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

效果很好。您有 10 个工作线程与这些调用相关wait,然后另外 10 个调度块调用signal,您\xe2\x80\x99 就可以了。

\n\n

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

\n\n
\n\n

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

\n\n
let semaphore = DispatchSemaphore(value: 0)\nsomeAsynchronousMethod {\n    // do something useful\n\n    semaphore.signal()\n}\nsemaphore.wait()\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n