代码之家  ›  专栏  ›  技术社区  ›  lurning too koad

Dispatch Semaphore会无意中死锁吗?

  •  0
  • lurning too koad  · 技术社区  · 6 年前

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

    0 回复  |  直到 6 年前
        1
  •  5
  •   Rob Md Fahim Faez Abir    6 年前

    简短回答:

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

    长答案:

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

    话虽如此,我们必须对线程池中的工作线程数量极其有限(目前每个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 ,你没事。

    但是,如果你增加 count 对于100(一种称为线程爆炸的情况),上述代码永远不会自行解决,因为 信号 调用正在等待与所有这些线程绑定的工作线程 等待 电话。所有已分派的任务均未包含 信号 通话将永远有机会继续。而且,当你耗尽工作线程时,这通常是一个灾难性的问题,因为任何试图使用GCD(用于相同的QoS)的东西都无法运行。


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

    let semaphore = DispatchSemaphore(value: 0)
    someAsynchronousMethod {
        // do something useful
    
        semaphore.signal()
    }
    semaphore.wait()
    

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

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