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:
- 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.
- 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.