Dispatch Semaphore Anti-Pattern

The Xcode 10 release notes mention a static analyzer feature that checks for a common performance anti-pattern when using Grand Central Dispatch, involving waiting on a callback using a semaphore.

1. For a function that needs to synchronously return a value from an asynchronous API, is there a more efficient solution than using a DispatchWorkItem as shown here?


2. The analyzer produces the following warning for the anti-pattern:

"Waiting on a callback using a semaphore creates useless threads and is subject to priority inversion; consider using a synchronous API or changing the caller to be asynchronous"

What is the priority inversion the warning refers to?

At the time of this writing, the analyzer doesn’t produce a warning for the anti-pattern in Swift as it does in Objective-C.



What is the priority inversion the warning refers to?

Let’s walk you through an example here:

  1. Imagine you have a subsystem that varnishes waffles. This subsystem needs to be thread safe, so you serialise all the work on a dispatch queue. Varnishing waffles is generally not a high priority task, so that queue uses a standard priority.

  2. You decide to give this subsystem an async API. There’s two entries points:

    • The first one submits waffles to varnish.

    • The second one returns the status of a varnishing operation.

  3. Most of the time this is fine. However, imagine you have a UI that you need to synchronously update based on the waffle varnish state. To do that you need to call the status API, but you need to results immediately so you use the synthetic synchronous approach.

This is where you see a priority inversion. The main thread generally runs with a high priority because it’s updating the UI. However, in this case the main thread is stuck waiting for the waffle varnishing queue to handle its status request, but the waffle varnishing queue runs at the default priority. Hence the priority inversion: High-priority work is stuck behind low-priority work.

This is more than just a performance issue. For example, on an iOS device in low power mode, the system might not service low-priority queues at all, which means that the UI could effectively deadlock.

We resolve this using a priority boost mechanism. If the main thread waits using a construct that tracks ownership, the system can boost the priority of the waffle varnishing subsystem while the UI thread is waiting.

One neat-o feature of this is that this priority boost mechanism can work across processes.

Coming back to

itself, this problem is complicated by a couple of things:
  • NSURLSession
    is backed by a whole bunch of interacting async subsystems.
  • Networking is typically not CPU bound; most of the time taken to complete a request is the I/O time.

Given that, it’s not clear to me how this priority boost mechanism would apply.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"