Swift Tricks

My own, mostly internal blog of Swift tips and tricks

Ensure that threads wait using semaphores

Basics

When you need to make sure that various threads wait for the availability of a shared resource (whether that be a file, the result of an API query, etc.) you can use GCD's DispatchSemaphore feature.

The simplest example:

let semaphore = DispatchSemaphore(value: 1)
semaphore.wait()  // requesting the resource
// Do something here
semaphore.signal() // releasing the resource

The value part of the DispatchSemaphore defines how many threads can access the resource at once. When you wait(), this requests the resource (so adds one to the counter) and signal() releases (or subtracts one from the counter). Whenever another thread hits wait() it will check if the counter is >= than value...if it is, it will wait until the resource is released.

So in the example above, the result is that you can be sure that // Do something here is only called by one thread at any given time. If multiple threads hit this code they will all wait for any previous thread to complete before continuing.

Another example

On the basis of this great post on Medium here is a more advanced example:

let higherPriority = DispatchQueue.global(qos: .utility)
let lowerPriority = DispatchQueue.global(qos: .utility)

let semaphore = DispatchSemaphore(value: 1)

func asyncPrint(queue: DispatchQueue, symbol: String) {
    queue.async {
        print("(symbol) waiting")
        semaphore.wait()  // requesting the resource
        
        for i in 0...10 {
            print(symbol, i)
        }
        
        print("(symbol) signal")
        semaphore.signal() // releasing the resource
    }
}

asyncPrint(queue: higherPriority, symbol: "A")
asyncPrint(queue: lowerPriority, symbol: "B")
Without semaphores, the result would be unprectable (depending on how the two threads decide to run). With semaphores however one thread will nicely wait for the other resulting in:
A waiting
B waiting
A 0
A 1
A 2
A 3
A 4
A 5
A 6
A 7
A 8
A 9
A 10
A signal
B 0
B 1
B 2
B 3
B 4
B 5
B 6
B 7
B 8
B 9
B 10
B signal

Waiting for async operations

You can also make threads wait until async operations complete - this will allow you to write a non-escaping, synchronously returning function even though it performs async operations within its logic. To do this, you must use Semphores with a value of 0 to not allow any further execution until the signal is reached:

let semaphore = DispatchSemaphore(value: 0)

var result: Bool? = nil
DoSomething.Async { (asyncResult)
    // Do whatever you need 
    result = asyncResult
    // Now we emit a signal
    semaphore.signal()
}

// Here we wait until the async operation completes
// (It may be a good idea to add a timeout in case your async takes too long or fails)
let waitResult = semaphore.wait(timeout: DispatchTime.now() + .seconds(5))
guard case .timedOut = waitResult else {
    // Timed out!
    return
}

// Result is ready to use!
print(result)

Things to look out for

Federico's Medium article does a great job at discussing these issues. In short:

  • It's a good idea to have all threads that should wait for each other to run using the same QoS
  • wait()-ing on the main thread will lead to UI freeze
  • Try to avoid thread deadlock where each thread is blocked by the other