我有三个名为queueA,queueB,queueC的调度线程
现在我想在queueB和queueC完成后执行queueA
所以我试图通过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")
}
}
}
答案 0 :(得分:4)
首先,在一个稍微迂腐的音符上,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提供了dispatch groups来解决您的问题。使用调度组,您可以将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 :(得分:2)
一些想法:
是的,您可以毫无问题地使用多个wait
来电。每个wait
需要一个signal
。
如果您这样做,您可能希望将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:)
是最简单的。