我得到了三个调度的线程,分别名为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
一些想法:
-
是的,您可以毫无问题地使用多个
wait
调用。您需要为每个signal
wait
。 -
如果你要这样做,你可能希望将
signal
调用放在这些依赖闭包的末尾,而不是它们的开头。现在,您正在等待这两个任务开始,而不是等待它们完成。 -
正如其他地方提到的,一个更好的机制是调度组。但是您不需要手动
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:)
最简单的。