【问题标题】:Can a Dispatch Semaphore inadvertently deadlock itself?Dispatch Semaphore 会无意中死锁吗?
【发布时间】:2020-05-28 00:03:32
【问题描述】:

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

【问题讨论】:

标签: ios grand-central-dispatch dispatchsemaphore


【解决方案1】:

简答:

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

长答案:

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

话虽如此,我们必须对线程池中工作线程的数量极为有限(目前每个 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")
}

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

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


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

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

    semaphore.signal()
}
semaphore.wait()

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

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

【讨论】:

  • 这是一个很好的答案。顺便说一句,为什么将信号量的值设置为0?难道我们不把值设置为我们希望能够同时访问共享资源的线程数,比如1
  • 你可以使用任何你需要的值。是的,当使用信号量进行同步时,1 是一个常见的值(同样,我们通常会首先使用其他同步机制,例如读写器或简单锁)。或者当使用它们来限制并发度时,同样,正信号量值是常见的。但是当做简单的“我想等到另一个线程向我发送信号”时,则使用 0。这仅取决于您的目的。对于我上面的例子,零是正确的。
  • 理论上是否有可能,例如,如果有 10 个不同的线程在一个特定的信号量上不断调用 wait() 和 signal()(例如在无限的 while 循环中)会长时间阻塞一个线程时间是因为例如出于某种原因,其中 9 个线程会比另一个线程更频繁地解锁?我的意思是下一个线程解锁的顺序是随机的,不是吗?如果是,那么理论上 1 个线程会比其他 9 个线程挂起的时间要长得多(所以不完全是死锁,但有时仍然可能是个问题)?
  • 我的理解是调度信号量是“公平的”并且不会饿死任何线程。 (文档中不再明确引用信号量的 FIFO 行为,但 source 清楚地表明它是 FIFO。)这与 os_unfair_lock 形成鲜明对比,后者明确 可以 饿死线程(但公认的性能要好得多)。显然,您可以使用 GCD 队列来明确保证 FIFO 行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
相关资源
最近更新 更多