The popular Concurrent-Ruby library has a Concurrent::Event
class that I find wonderful. It very neatly encapsulates the idea of, “Some threads need to wait for another thread to finish something before proceeding.”
It only takes three lines of code to use:
- One to create the object
- One to call
.wait
to start waiting, and - One to call
.set
when the thing is ready.
All the locks and booleans you’d need to use to create this out of other concurrency primitives are taken care of for you.
To quote some of the documentation, along with with a sample usage:
Old school kernel-style event reminiscent of Win32 programming in C .
When an
Event
is created it is in theunset
state. Threads can choose to#wait
on the event, blocking until released by another thread. When one thread wants to alert all blocking threads it calls the#set
method which will then wake up all listeners. Once anEvent
has been set it remains set. New threads calling#wait
will return immediately.
require 'concurrent-ruby' event = Concurrent::Event.new t1 = Thread.new do puts "t1 is waiting" event.wait puts "event ocurred" end t2 = Thread.new do puts "t2 calling set" event.set end [t1, t2].each(&:join)
which prints output like the following
t1 is waiting
t2 calling set
event occurred
(Several different orders are possible because it is multithreaded, but ‘t2 calling set’ always comes out before ‘event occurred’.)
Is there something like this in Swift on iOS?
CodePudding user response:
I think the closest thing to that is the new async/await
syntax in Swift 5.5. There's no equivalent of event.set
, but await
waits for something asynchronous to finish. A particularly nice expression of concurrency is async let
, which proceeds concurrently but then lets you pause to gather up all the results of the async let
calls:
async let result1 = // do something asynchronous
async let result2 = // do something else asynchronous at the same time
// ... and keep going...
// now let's gather up the results
return await (result1, result2)
CodePudding user response:
You can achieve the result in your example using a Grand Central Dispatch DispatchSemaphore - This is a traditional counting semaphore. Each call to signal
increments the semaphore. Each call to wait
decrement the semaphore and if the result is less than zero it blocks and waits until the semaphore is 0
let semaphore = DispatchSemaphore(value: 0)
let q1 = DispatchQueue(label:"q1", target: .global(qos: .utility))
let q2 = DispatchQueue(label:"q2", target: .global(qos: .utility))
q1.async {
print("q1 is waiting")
semaphore.wait()
print("event occurred")
}
q2.async {
print("q2 calling signal")
semaphore.signal()
}
Output:
q1 is waiting
q2 calling signal
event occurred
But this object won't work if you have multiple threads that want to wait. Since each call to wait
decrements the semaphore the other tasks would remain blocked.
For that you could use a DispatchGroup
. You call enter
before you start a task in the group and leave
when it is done. You can use wait
to block until the group is empty, and like your Ruby object, wait
will not block if the group is already empty and multiple threads can wait
on the same group.
let group = DispatchGroup()
let q1 = DispatchQueue(label:"q1", target: .global(qos: .utility))
let q2 = DispatchQueue(label:"q2", target: .global(qos: .utility))
q1.async {
print("q1 is waiting")
group.wait()
print("event occurred")
}
group.enter()
q2.async {
print("q2 calling leave")
group.leave()
}
Output:
q1 is waiting
q2 calling leave
event occurred
You generally want to avoid blocking threads on iOS if possible as there is a risk of deadlocks and if you block the main thread your whole app will become non responsive. It is more common to use notify
to schedule code to execute when the group becomes empty.
I understand that your code is simply a contrived example, but depending on what you actually want to do and your minimum supported iOS requirements, there may be better alternatives.
- DispatchGroup to execute code when several asynchronous tasks are complete using
notify
rather thanwait
- Combine to process asynchronous events in a pipeline (iOS 13 )
- Async/Await (iOS 15 )