一次多次调用 DispatchSemaphore 的 wait() 是否安全?



我得到了三个调度的线程,分别名为queueA,queueB,queueC.
现在我希望队列A在queueB和queueC之后执行.
所以我试图通过DispatchSemaphore来实现它.
我的问题是:
在一个线程中一次调用wait()两次以使信号量2安全吗?

self.semaphore.wait()  // -1
self.semaphore.wait()  // -1

以下是整个测试代码:

class GCDLockTest {
let semaphore = DispatchSemaphore(value: 0) 

func test() {

let queueA = DispatchQueue(label: "Q1")
let queueB = DispatchQueue(label: "Q2")
let queueC = DispatchQueue(label: "Q3")
queueA.async {
self.semaphore.wait()  // -1
self.semaphore.wait()  // -1
print("QueueA gonna sleep")
sleep(3)
print("QueueA woke up")                
}
queueB.async {
self.semaphore.signal()  // +1
print("QueueB gonna sleep")
sleep(3)
print("QueueB woke up")
}
queueC.async {
self.semaphore.signal()  // +1
print("QueueC gonna sleep")
sleep(3)
print("QueueC wake up")
}
}
}

首先,稍微迂腐一点,signal会增加信号量,wait会递减信号量(除非它是零,在这种情况下它会等待)。

在一个

线程中一次调用 wait() 两次以使信号量 2 [原文如此]安全吗?

信号量操作保证是线程安全的,如果没有,那么拥有它们就没有意义,因此您正在做的事情将正常工作。您确实可以调用wait两次,以在概念上获取两次资源。

但是,您有一个阻止的后台线程。这是一件坏事,因为用于在调度队列上执行块的线程不是在需要时创建的,而是从根据各种因素(如处理器内核数)调整大小的池中分配的。队列 A 上的块将占用一个线程,直到队列 B 和队列 C 线程都发出信号量信号。

最坏的情况发生在进入函数test()时,线程池中只剩下一个线程。如果队列 A 上的块在其他两个块中的任何一个之前抓取它,您将有一个死锁,因为 A 将等待信号量,B 和 C 将在等待 A 完成,以便它们可以有一个线程。

最好在其他两个线程准备好启动 A 之前不要启动它。这可以通过在正确的时间在主线程上执行块来完成。像这样:

class GCDLockTest {
var cFinished = false
var bFinished = false 
func test() {
let queueA = DispatchQueue(label: "Q1")
let queueB = DispatchQueue(label: "Q2")
let queueC = DispatchQueue(label: "Q3")
queueB.async {
DispatchQueue.main.async
{
bFinished = true
if cFinished
{
queueA.async {
print("QueueA gonna sleep")
sleep(3)
print("QueueA woke up")                
}
}
}
print("QueueB gonna sleep")
sleep(3)
print("QueueB woke up")
}
queueC.async {
DispatchQueue.main.async
{
cFinished = true
if bFinished
{
queueA.async {
print("QueueA gonna sleep")
sleep(3)
print("QueueA woke up")                
}
}
}
print("QueueC gonna sleep")
sleep(3)
print("QueueC wake up")
}
}
}

在上面,您不需要任何信号量或其他同步,因为它隐含在所有同步工作都是在串行的主队列上完成的。 即启动 A 的两个块永远不能同时运行。

这是一种方法,但Apple为您的问题提供了调度组。使用调度组,您可以将 B 和 C 添加到组中,并让他们告诉组何时准备好启动 A。

class GCDLockTest {
func test() {
let group = DispatchGroup()
let queueA = DispatchQueue(label: "Q1")
let queueB = DispatchQueue(label: "Q2")
let queueC = DispatchQueue(label: "Q3")
group.enter()
queueB.async {
group.leave()
print("QueueB gonna sleep")
sleep(3)
print("QueueB woke up")
}
group.enter()
queueC.async {
group.leave()
print("QueueC gonna sleep")
sleep(3)
print("QueueC wake up")
}
group.notify(queue: queueA) {
print("QueueA gonna sleep")
sleep(3)
print("QueueA woke up")                
}
}

在启动 B 和 C 之前,将进入该组。然后在启动 B 和 C 之后,我们在组中放置一个通知块,这样当它们都离开组时,A 的块在正确的队列上启动。

另请参阅 https://developer.apple.com/documentation/dispatch/dispatchgroup

一些想法:

  1. 是的,您可以毫无问题地使用多个wait调用。您需要为每个signalwait

  2. 如果你要这样做,你可能希望将signal调用放在这些依赖闭包的末尾,而不是它们的开头。现在,您正在等待这两个任务开始,而不是等待它们完成。

  3. 正如其他地方提到的,一个更好的机制是调度组。但是您不需要手动enter/leave通话。您可以使用group参数来async

    class GCDLockTest {
    func test() {
    let group = DispatchGroup()
    let queueA = DispatchQueue(label: "Q1")
    let queueB = DispatchQueue(label: "Q2")
    let queueC = DispatchQueue(label: "Q3")
    queueB.async(group: group) {
    print("QueueB gonna sleep")
    sleep(3)
    print("QueueB woke up")
    }
    queueC.async(group: group) {
    print("QueueC gonna sleep")
    sleep(3)
    print("QueueC wake up")
    }
    group.notify(queue: queueA) {
    print("QueueA gonna sleep")
    sleep(3)
    print("QueueA woke up")                
    }
    }
    }
    

    仅当您调度的内容本身是异步的并且具有自己的完成处理程序时,才需要手动enter/leave调用。但对于这样的例子,async(group:)最简单的。

最新更新