What's the best practices For GCD?

Hi Swift Community!

I'm trying to understand the best practices for using GCD after watching Modernizing Grand Central Dispatch Usage and reading the discussion here and here.

So it seems like using custom serial queue will give call site an overcommited queue, which can lead to thread explosion. But we also can't directly give work item with DispatchQueue.global().async as it can block some threads and cause GCD to create new threads for other work items.
Then the only option left is custom concurrent queue? But then it is also not recommended to use custom concurrent queue suggested in multiple resources. (And I guess it would also cause the same thread explosion issue like DispatchQueue.global().async?)

Then looks like every approach has its limitations. If I understand correctly, here are the recommended practices.

  1. For light weight tasks, (e.g like just reading / write to some local variables), unfair lock is still preferred compared to GCD queues.
  2. Custom serial queue is still preferred whenever a queue is needed and should avoid having a long live one when possible, and numbers should be limited to avoid thread explosion.
  3. When the codebase has too many custom serial queues, we should use target queue hierarchy to control and throttle the work items that are submitted to the default target queue (which is default global queue).
  4. Only use custom concurrent queues when the benchmark result shows that using it indeed has performance benefits.
  5. DispatchQueue.global().async is still useful as long as the submitted items are lightweight enough and will finish very quick.

Are those the right practices to follow? Or have I missed something important in the decision making process? Thank you!

Unless you're dynamically creating serial queues, which you shouldn't be, thread explosions are impossible, as a serial queue uses, at most, one thread.

For best practices I suggest watching that WWDC video several times, as it includes several approaches you can take, but I think you generally have it. However, GCD best practices really depend on your intended usage. When I rewrote Alamofire's internals I followed generally the same practices:

  1. State mutations that need to be exposed publicly are behind a simple lock so they can performed atomically and synchronously. You should really never use GCD when a simple lock will do.
  2. There are only a few, long lived serial queues on which the framework does all of its internal work, and by default those queues are, in fact, targeted off the same root queue. This allows lockless data sharing in some areas, while allowing other areas to be targeted onto arbitrary queues.
  3. No sync calls. If I need synchronous updates I use a lock, otherwise everything else goes through queues using async. This ensures deadlocks aren't possible.
  4. For particular uses, when taking queues from users, they aren't necessarily used directly. Instead, they're always targeted by an additional serial queue to ensure runtime behavior remains as expected.
  5. When needed, the base serial queues are used to underly OperationQueues, such as that needed for the URLSession delegates. This ensures there's no thread hops between the delegate callbacks and state updates unless I want there to be.