Tuesday, March 16, 2021 [Tweets] [Favorites]

Underused and Overused GCD Patterns

David Smith:

Underused GCD patterns:

Making a serial queue that’s less aggressive about creating threads (“non-overcommit”):

let q = DispatchQueue(…, target: DispatchQueue.global())

All serial queues ultimately target a global queue, which is responsible for actually executing the work; but, for historical reasons, the default target is an overcommit queue. That means it has a higher max thread cap and creates threads more aggressively. This avoids that.

Multiplexing work onto a single serial queue efficiently:

theQueueWeAreAlreadyOn.async { … }

This pushes towards architectures with small numbers of well-known queues and/or explicitly passing queues into things.

Probably overused GCD patterns:

  • Global queues as anything but targets
  • Almost any use of concurrent queues
  • Queues as locks; os_unfair_lock is more efficient (sadly a little trickier to use in Swift; no ideal solution here yet)
  • Turning async into sync with semaphores

Another overused GCD pattern I forgot: manually specifying QoS. Most of the time you can/should rely on automatic propagation, you only need to do it manually if you want to override that for some reason.

Pierre Habouzit:

even when you do not set a target queue you have the autorelasepool of last resort, but you should never rely on it ever.

Nowadays you should always pass the “autoreleasing” attribute at queue creation. we couldn’t make it default due to things too horrible to mention.

See also: Modernizing Grand Central Dispatch Usage.

Previously:

Update (2021-03-22): Jonathan Joelson:

The lack of official GCD documentation is baffling given the complexity. Why isn’t any of this stuff explained here?

Pierre Lebeaupin:

I haven’t found any better way to avoid these scenarios than by not blocking in the kernel unless you know for a fact that doing so will arbitrarily scale; and consequently, you need only launch a limited number of tasks: with rare exceptions you will not need libdispatch to schedule an unpredictable number of them to reach full core occupation.

[…]

If that queue is a serial queue, however, you have a real problem: as soon as your task has “returned” after calling the asynchronous API, there is nothing that prevents another unrelated task on the serial queue from launching, and finding the state not as it was when you usually start a task, but as you left it at the point you needed to call the async API.

So that leaves you with a rotten choice: either keep the synchronous call instead and risk thread explosion, or factor your code so your state is consistent at the point you hand over to the asynchronous API. But, wait. I don’t call that a choice: I call that a fake dilemma. Presumably, you were using the serial queue for the purpose of state protection, and if that queue can’t ensure its purpose, then the problem is with the queue. I haven’t found any way to “reserve” the queue until the completion has run, that does not seem to be possible with libdispatch. There is no simple solution here, to put it bluntly. If you have few enough serial queues in that situation, then I give you special dispensation to perform blocking calls from it, but in compensation every single such serial queue you create has to be considered equivalent to creating a thread, resource-wise.

David Smith:

dispatch_workloop_t is great stuff btw. It’s a serial dispatch queue that runs blocks in priority order instead of first-in-first-out. This can be considerably more efficient when you just need the thread + mutual exclusion aspects of a queue.

But it is not available in Swift.

3 Comments

Great, more GCD lore. How about updating the docs instead?

@Nick: On the one hand, I agree the gap between the documentation ("don't call anything that might ever wait in the kernel", while failing to admit this amounts to newly creating every single bit of code) and the lore ("focus on a small number of clearly identified queues") has become ridiculous. On the other hand, no amount of doc updating will solve the basic situation that Grand Central Dispatch is a douchebag.

@ Nick: see the thread: "(Yes I’m going through these filing radars to either improve the clarity of the API, or improve the docs)"

Stay up-to-date by subscribing to the Comments RSS Feed for this post.

Leave a Comment