I would like to make a queue wait for a short period while it is looping. I am considering my options and was testing out suspending a resuming a queue but that seems to require
Since this has issue caused me a lot of grief over the years, I figured I'd share the benefit of that suffering experience:
Any time you block (which includes calling sleep
) in a work unit* submitted to GCD, you are creating a situation with the potential for thread starvation. Worse yet, if your work unit's blocking is due to the use of synchronization primitives (i.e. semaphores, locks, etc), or if multiple work units are interlocked using these primitives, thread starvation has the potential to cause deadlocks. More in a minute, but here's the short version:
If you block in work units submitted to GCD:
- At best, you're using GCD inefficiently.
- At worst, you're exposing yourself to the potential for starvation, and therefore (potentially) deadlocks.
Here's why: The OS has a per-process thread limit. Changing the per-process thread limit is not impossible, but in practical terms, doing so is rarely worth it. GCD has it's own queue width limit (number of threads it will use to service a concurrent queue). (Although the details are complicated, it's worth noting here that creating multiple concurrent queues will not, generally speaking, get around this limit.) By definition, GCD's limit is lower than the per-process thread limit. Furthermore, GCD's queue width limit is an undocumented implementation detail. Empirically, at the time of this writing, on OS X, I've observed that the limit appears to be 64. Because this limit is an undocumented implementation detail, you cannot count on knowing what it is. (It is also not possible to change it using public API.) Ideally speaking, your code should be written such that it would still execute to completion, albeit slowly, if GCD changed the queue width limit to be 1. (It could also be argued that the same should be true even if that one thread also happens to be the main thread, but that makes the task unnecessarily harder, and it seems safe to assume that there will always be at least one background thread, since it'd hardly be worth using GCD if there weren't.)
How would you do this? If you're blocking while waiting for I/O, you would want to consider transitioning your code to use the dispatch_io
family of calls. For a quasi-busy wait situation (i.e. the original question), you could use dispatch_after
to check back on something after a set amount of time. For other cases, dispatch timers or dispatch sources may be appropriate.
I'll be the first to admit that it is not always practical (let alone expedient) to completely avoid blocking in work units submitted to GCD. Furthermore, the combination of GCD and Objective-C block-syntax makes it very simple to write expressive, easy-to-read code for avoiding situations where you would block the UI thread by moving blocking/synchronous operations to a background thread. In terms of expediting development, this pattern is extremely helpful, and I use it all the time. That said, it's worth knowing that enqueuing a work unit with GCD that blocks consumes some amount of a finite resource (i.e. number of threads available) whose size you, pedantically speaking, can't know (because it's an undocumented implementation detail, and could change at any time) and, practically speaking, can't control (because you can't set/change GCD's queue width using public API.)
With respect to the original question: Busy-waiting (for example, by calling sleep
or usleep
) is one of the most avoidable, and least defensible ways to block in a work unit submitted to GCD. I will go further and make the bold statement that there is always a better, if less expeditious-to-develop, way to implement any operation that can be implemented by busy-waiting in a GCD work unit.
* I'm using "work unit" to refer to Objective-C blocks or function pointers/arguments submitted to GCD for execution to limit confusion with the work "blocking", by which I mean "doing something that results in your thread being suspended in the kernel".