Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any reason to not use sleep in a Grand Central Dispatch queue?

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 several moving parts. So I am considering using sleep or usleep instead. That is more of a general threading function and would like to know if I should avoid using sleep and instead stick with GCD options to make a queue pause.

I found one related question but that answer shows that he was just missing an include. Are there any concerns with mixing sleep calls in with GCD queues?

iphone - is it ok to use usleep on a secondary thread on Grand Central Dispatch?

like image 995
Brennan Avatar asked Jul 19 '11 00:07

Brennan


2 Answers

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".

like image 114
ipmcc Avatar answered Sep 20 '22 05:09

ipmcc


You can use sleep, but as you mentioned, do it off of the main thread, as you should never tie up the main thread.

But, if you need a small sleep, the amount of time may not be precise, and is unlikely to be, but the thread will be woken up at least after the amount of sleep, depending on what else may be using the cpu.

But, I see no problem with using sleep as it will at least give other threads/applications, a chance to run.

like image 35
James Black Avatar answered Sep 19 '22 05:09

James Black