一次多次调用DispatchSemaphore的wait()是否安全? [英] Is this safe to call wait() of DispatchSemaphore several times at one time?

查看:115
本文介绍了一次多次调用DispatchSemaphore的wait()是否安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我得到了三个名为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")

        }
    }
}

解决方案

首先,在一个略带ped讽的音符上,是signal增大了信号量,而wait减小了信号量(除非它为零,否则为0).它要等待哪种情况).

在一次线程中两次调用wait()以使信号量2 [sic]是否安全?

信号量动作被保证是线程安全的,如果没有,那就没有意义了,所以您在做什么就可以了.实际上,您可以两次调用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之后,我们在该组中放置一个notify块,以便当他们都离开该组时,在正确的队列中启动A的块.

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

I got three dispatched threads named queueA, queueB, queueC.
Now I want the queueA executed after queueB and queueC done.
So I tried to implement it by DispatchSemaphore.
My Problem is:
Is this safe to call wait() two times in a thread at one time to make the semaphore 2?

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

The following is the entire test code:

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")

        }
    }
}

解决方案

First of all, on a slightly pedantic note, it is signal that increments the semaphore and wait that decrements it (unless it is zero, in which case it waits).

Is this safe to call wait() two times in a thread at one time to make the semaphore 2 [sic]?

Semaphore actions are guaranteed to be thread safe, there would be no point in having them if not, so what you are doing will work fine. You can indeed call wait twice to conceptually grab a resource twice.

However, you have a background thread that is blocking. This is a bad thing because threads used to execute blocks on dispatch queues aren't created when they are needed, they are allocated from a pool that is sized based on various things like number of processor cores. Your block on queue A will tie up a thread until the queue B and queue C threads both signal the semaphore.

The worst case scenario occurs when you enter function test() with only one thread remaining in the thread pool. If the block on queue A grabs it before either of the other two blocks, you will have a dead lock because A will be waiting on a semaphore and B and C will be waiting on A to finish so they can have a thread.

It would be better to not start A until the other two threads are ready to kick it off. This can be done by executing a block on the main thread at the right time. Something like this:

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")

        }
    }
}

In the above, you don't need any semaphores or other synchronisation because it is implicit in that all the synchronisation work is done on the main queue which is serial. i.e. the two blocks that start A can never be running at the same time.

That's one way to do it, but Apple provides dispatch groups for exactly your problem. With dispatch groups you can add B and C to a group and have them tell the group when they are ready for A to start.

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")                
    }
}

Before starting each of B and C the group is entered. Then after starting B and C, we place a notify block in the group so that when they both leave the group, the block for A is started on the right queue.

See also https://developer.apple.com/documentation/dispatch/dispatchgroup

这篇关于一次多次调用DispatchSemaphore的wait()是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆