Is This Safe to Call Wait() of Dispatchsemaphore Several Times at One Time

Is this safe to call wait() of DispatchSemaphore several times at one time?

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

Should semaphore wait and signal always be called from separate queues?

You ask:

Should semaphore wait and signal always be called from separate queues?

Semaphores are always be called from separate threads. That’s the purpose of semaphores, for one thread to send a signal for which another thread will wait. That means that it’s safe to call semaphores from the same concurrent queue (because individually dispatched tasks run on different worker threads), but it’s not safe to call semaphores from the same serial queue. Obviously, it’s also safe to call semaphores from different queues. The main point is that it has to be different threads.

You shared a quote from that document, and everything the author said is absolutely correct. The wait and signal calls must be done from different threads. And we never want to wait on the main thread for some signal being sent by some other, time consuming, process.

You then went on to say:

Most of the examples of semaphore usually call wait and signal from the same queue and that seems to work fine too. Am I missing something here?

// Pseudocode from: https://khanlou.com/2016/04/the-GCD-handbook/
// on a background queue
let semaphore = DispatchSemaphore(value: 0)
doSomeExpensiveWorkAsynchronously(completionBlock: {
semaphore.signal()
})
semaphore.wait()
//the expensive asynchronous work is now done

A few observations:

  1. This pattern only works if signal and wait are on separate threads. If they were the same thread, this would deadlock. So clearly the author is assuming that they’re on different threads.

  2. You seem to be implying that these two calls are “on the same queue”. That’s not a valid assumption (and, frankly, is quite unlikely). We would need to see the implementation of that “expensive asynchronously” method to be sure. But when you see a closure like this, it generally means that the method dispatched this closure to some GCD queue of its own choosing. And we have no way of knowing which it used. (You’d have to look at its implementation to be sure.) But it’s unlikely to be the same queue. And this code presumes that it must be a different thread.

  3. This whole pattern that you’ve shared with us here is ill-advised. It’s effectively taking an asynchronous method, using a semaphore to make it behave synchronously, but the code comment suggests that this whole thing was dispatched to a background queue (to avoid blocking the main thread) thereby making it asynchronous again. That’s a bit tortured. You really should just go ahead and call this expensive/asynchronous method from the main thread (which is safe, because it runs asynchronously) and lose the semaphore entirely. Maybe the author was contorting himself/herself to illustrate how one could use semaphores, but it’s a horrible example.

How to work Dispatch_Sempahore when first call signal and

Every .signal is '+1', and every .wait is '-1' or block as documented, and code that demo is

let semaphore = DispatchSemaphore(value: 0)
semaphore.signal() // = 1
semaphore.signal() // = 2
semaphore.signal() // = 3

semaphore.wait() // = 2 - pass
semaphore.wait() // = 1 - pass
semaphore.wait() // = 0 - pass
semaphore.wait() // = -1 - hang - waiting for new signal()

Here is from Apple Documentation

You increment a semaphore count by calling the signal() method, and
decrement a semaphore count by calling wait() or one of its variants
that specifies a timeout.

@discardableResult func signal() -> Int
Discussion
Increment the counting semaphore. If the previous value was less than zero,
this function wakes a thread currently waiting

func wait()
Discussion
Decrement the counting semaphore. If the resulting value is less than zero,
this function waits for a signal to occur before returning.

Safe to signal semaphore before deinitialization just in case?

You have stumbled into a feature/bug in DispatchSemaphore. If you look at the stack trace and jump to the top of the stack, you'll see assembly with a message:

BUG IN CLIENT OF LIBDISPATCH: Semaphore object deallocated while in use

E.g.,

Sample Image

This is because DispatchSemaphore checks to see whether the semaphore’s associated value is less at deinit than at init, and if so, it fails. In short, if the value is less, libDispatch concludes that the semaphore is still being used.

This may appear to be overly conservative, as this generally happens if the client was sloppy, not because there is necessarily some serious problem. And it would be nice if it issued a meaningful exception message rather forcing us to dig through stack traces. But that is how libDispatch works, and we have to live with it.

All of that having been said, there are three possible solutions:

  1. You obviously have a path of execution where you are waiting and not reaching the signal before the object is being deallocated. Change the code so that this cannot happen and your problem goes away.

  2. While you should just make sure that wait and signal calls are balanced (fixing the source of the problem), you can use the approach in your question (to address the symptoms of the problem). But that deinit approach solves the problem through the use of non-local reasoning. If you change the initialization, so the value is, for example, five, you or some future programmer have to remember to also go to deinit and insert four more signal calls.

    The other approach is to instantiate the semaphore with a value of zero and then, during initialization, just signal enough times to get the value up to where you want it. Then you won’t have this problem. This keeps the resolution of the problem localized in initialization rather than trying to have to remember to adjust deinit every time you change the non-zero value during initialization.

    See https://lists.apple.com/archives/cocoa-dev/2014/Apr/msg00483.html.

  3. Itai enumerated a number of reasons that one should not use semaphores at all. There are lots of other reasons, too:

    • Semaphores are incompatible with new Swift concurrency system (see Swift concurrency: Behind the scenes);
    • Semaphores can also easily introduce deadlocks if not precise in one’s code;
    • Semaphores are generally antithetical to cancellable asynchronous routines; etc.

    Nowadays, semaphores are almost always the wrong solution. If you tell us what problem you are trying to solve with the semaphore, we might be able to recommend other, better, solutions.


You said:

However, if the async function returns before deinit is called and the view controller is deinitialized, then signal() is called twice, which doesn't seem problematic. But is it safe and/or wise to do this?

Technically speaking, over-signaling does not introduce new problems, so you don't really have to worry about that. But this “just in case” over-signaling does have a hint of code smell about it. It tells you that you have cases where you are waiting but never reaching signaling, which suggests a logic error (see point 1 above).

When to use Semaphore instead of Dispatch Group?

Conceptually, both of DispatchGroup and Semaphore serve the same purpose (unless I misunderstand something).

The above is not exactly true. You can use a semaphore to do the same thing as a dispatch group but it is much more general.

Dispatch groups are used when you have a load of things you want to do that can all happen at once, but you need to wait for them all to finish before doing something else.

Semaphores can be used for the above but they are general purpose synchronisation objects and can be used for many other purposes too. The concept of a semaphore is not limited to Apple and can be found in many operating systems.

In general, a semaphore has a value which is a non negative integer and two operations:

  • wait If the value is not zero, decrement it, otherwise block until something signals the semaphore.

  • signal If there are threads waiting, unblock one of them, otherwise increment the value.

Needless to say both operations have to be thread safe. In olden days, when you only had one CPU, you'd simply disable interrupts whilst manipulating the value and the queue of waiting threads. Nowadays, it is more complicated because of multiple CPU cores and on chip caches etc.

A semaphore can be used in any case where you have a resource that can be accessed by at most N threads at the same time. You set the semaphore's initial value to N and then the first N threads that wait on it are not blocked but the next thread has to wait until one of the first N threads has signaled the semaphore. The simplest case is N = 1. In that case, the semaphore behaves like a mutex lock.

A semaphore can be used to emulate a dispatch group. You start the sempahore at 0, start all the tasks - tracking how many you have started and wait on the semaphore that number of times. Each task must signal the semaphore when it completes.

However, there are some gotchas. For example, you need a separate count to know how many times to wait. If you want to be able to add more tasks to the group after you have started waiting, the count can only be updated in a mutex protected block and that may lead to problems with deadlocking. Also, I think the Dispatch implementation of semaphores might be vulnerable to priority inversion. Priority inversion occurs when a high priority thread waits for a resource that a low priority has grabbed. The high priority thread is blocked until the low priority thread releases the resource. If there is a medium priority thread running, this may never happen.

You can pretty much do anything with a semaphore that other higher level synchronisation abstractions can do, but doing it right is often a tricky business to get right. The higher level abstractions are (hopefully) carefully written and you should use them in preference to a "roll your own" implementation with semaphores, if possible.

Does DispatchSemaphore wait for specific thread objects?

Let’s tackle these one at a time:

someAsyncMethod {

serialQueue.async {

// When someAsyncMethod returns, this queue, behind the scenes,
// creates a Thread/NSThread object and approaches the
// semaphore and it is this thread object that decrements
// the semaphore counter.

// The next time someAsyncMethod returns, a new thread object
// is created behind the scenes (by the queue) and this thread
// object is made to wait until something (anything) signals
// it is done.

semaphore.wait()

// do work...
someMethod()
}
}

First, a minor detail, but async doesn’t “create” threads. GCD has a pool of worker threads, and async just grabs one of these idle worker threads. (It’s why GCD is performant, as it avoids creating/destroying threads all the time, which is a costly exercise. It draws upon its pool of worker threads.)

The second time you call async on the same serial queue, you might get the same thread the next time. Or you might get a different worker thread from the pool. You have no assurances either way. Your only assurance is that the queue is serial (because you defined
it as such).

But, yes, if the semaphore started with a count of 1, the first time it would just decrement the counter and carry on. And, yes, if that semaphore hadn’t yet been signaled by the time you got to that second wait, yes it would wait for a signal.

But the idea of using this non-zero dispatch semaphore in conjunction with a serial queue seems highly suspect. Usually you use serial queues to coordinate different tasks, or, in rare cases, use semaphores, but almost never both at the same time. Generally, the presence of semaphores, at all, is concerning, because there are almost always better solutions available.

You then had:

func someMethod() {

// do work...

// This task may ultimately be on a different thread than
// where it started despite being in the same queue
// (since not every task is guaranteed to run on the same thread)
// but it doesn't matter, any object can signal the semaphore.

semaphore.signal()

}

This code, because it was called from inside the previous code block’s serialQueue.async, will absolutely still be running on the same thread from which you called someMethod.

So, therefore, this code doesn’t quite make sense. You would never signal from the same thread that would call wait on the same thread. The whole point of semaphores is that one thread can wait for a signal from a different thread.

E.g. it might make sense if, for example, someMethod was doing something equivalent to:

func someMethod() {
someOtherQueue.async {
// do work...

// Because this is dispatched to a different queue and we know is
// now running on a different thread, now the semaphore has some
// utility, because it only works if it’s a different thread than
// where we’ll later call `wait`...

semaphore.signal()
}
}

Now, all of this begs the question about the purpose of the serial queue in conjunction with this semaphore. I’m afraid that in the attempt to abstract this away from the unrelated details of your code base, you’ve made this a bit too abstract. So it’s hard to advise you on a better pattern without understanding the broader problem this code was attempting to solve.

But, with no offense intended, there almost certainly are going to be much better approaches. We can’t just can’t advise on the basis of the information provided thus far.


You concluded with two questions:

  1. Do semaphores respond to specific thread objects/instances?

Nope. It’s for one thread to signal to another, different, thread. But there are no other constraints other than that.


  1. Is a new thread object/instance created every time someAsyncMethod returns and enters the queue?

As I hope I’ve made clear above, the closure dispatched to serial queue with async may end up on the same worker thread or may end up on another worker thread.

But that’s not the real concern here. The real concern is that the wait and signal calls are made from separate threads, not whether subsequent wait calls are.

Dispatch semaphore signal on wait timeout

dispatch_semaphore_wait() decrements the counting semaphore and waits
if the resulting value is less than zero. If a timeout occurs, this
decrement is reversed, so you don't have to adjust the count manually.

This is not apparent (to me) from the documentation, but consistent with
the fact that a negative count indicates that threads are waiting on the
semaphore. See also this comment in the source code:

// If the internal value is negative, then the absolute of the value is
// equal to the number of waiting threads. ...

You can also verify it by printing the debugDescription of the
semaphore, the output shows the current value:

let sem = dispatch_semaphore_create(0)

NSLog("%@", sem.debugDescription)
// <OS_dispatch_semaphore: semaphore[0x100514a70] = { ..., value = 0, orig = 0 }>
// --> Initial value is 0

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
NSLog("%@", sem.debugDescription)
// <OS_dispatch_semaphore: semaphore[0x100514a70] = { ..., value = -1, orig = 0 }>
// --> One thread is waiting, value is -1.
}

let ret = dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 2*Int64(NSEC_PER_SEC)))
NSLog("%@", sem.debugDescription)
// <OS_dispatch_semaphore: semaphore[0x100514a70] = { ..., value = 0, orig = 0 }>
// --> Time out, value is 0 again.

What is the purpose of Semaphore.wait(timeout: .now())?

The purpose is what the comment says:

wait() is used to drop new notifications if old ones are still processing, to avoid queueing up a bunch of stale data.

and it works as follows:

  • The semaphore is created with a value of one.
  • When metadataOutput is called the first time, wait(timeout: .now())
    succeeds and decrements the value of the semaphore to zero.
    The processing of the data begins.
  • If metadataOutput is called again before the processing has
    completed, the semaphore still has a value of zero.
    Then wait(timeout:) would wait for the semaphore to become
    positive again, but since the timeout value is now(), it fails
    immediately and returns .timedOut.
    The effect is that the incoming data is ignored, the metadataOutput
    callback method returns immediately.
  • When the data processing on the has completed,
    the semaphore is signaled, which increases the value to one.
    As a consequence, the next time the callback is called,
    waiting for the semaphore will succeed and the data is processed again.

So in short:

  • wait(timeout: .now()) returns .success if a
    previously submitted block has signaled completion, in that case
    a new block is submitted for processing the incoming data.
  • wait(timeout: .now()) returns .timedOut if a previously
    submitted block is still running, in that case the incoming data
    is ignored.

Is DispatchSemaphore a good replacement for NSLock?

Yes they have the same function, both to deal with producer-consumer problem.

Semaphore allows more than one thread to access a shared resource if it is configured accordingly. You can make the execution of the blocks in the same concurrent dispatchQueue.

{
semaphore.wait()
// do things...
semaphore.signal()
}

Actually the same applies to Lock, if you only want one thread to touch the resource at one time, in the concurrent way.

I found this to be helpful: https://priteshrnandgaonkar.github.io/concurrency-with-swift-3/



Related Topics



Leave a reply



Submit