Synchronizing GCD Dispatch Queues

Let's consider some use cases of GCD dispatch queues. They are often used in producer-consumer relationships for doing asynchronous work, either in a serial or parallel fashion.

In rare cases, you may need to synchronize the work on your queue with another thread. However, a dispatch queue does not operate on a given thread, and combining synchronization primitives with each block placed on the queue would be complicated and fragile.

A better method is to pause and resume the execution of a dispatch queue using dispatch_suspend and dispatch_resume.

dispatch_suspend is Asynchronous

But, there's something curious about dispatch_suspend. Apple's documentation for the function says:

The suspension occurs after completion of any blocks running at the time of the call.

The documentation doesn't tell us whether dispatch_suspend waits for these blocks to complete before returning, so I created a small test program to explore this. It tests this by queueing a block that waits on a condition that only becomes true if dispatch_suspend is asynchronous. If dispatch_suspend is synchronous, the application will hang.

When running this program on OS X 10.9, the program shows dispatch_suspend is an asynchronous process, so it can't be used on its own to synchronize a thread with work on a dispatch queue directly. However, there is a way we can build on top of it.

Barrier Blocks

Another facility provided by dispatch queues are barrier blocks. Barrier blocks work on both serial and concurrent dispatch queues. When submitted to a queue, all blocks ahead of the barrier will complete before the barrier starts execution, and all blocks behind the barrier will only start executing once the barrier is complete. For serial queues, this is the same as queueing any block. For concurrent queues, this ensures ordering of work and is especially handy for our purposes. Submitting a barrier block can be done with dispatch_barrier_async and dispatch_barrier_sync (and their function variants).

Synchronizing Queues

Armed with this knowledge, we can create a blocking synchronize the work done on a dispatch queue with an external thread like so:

dispatch_barrier_sync(queue, ^{ dispatch_suspend(queue); });

// Do some stuff

dispatch_resume(queue);

Note this doesn't only wait for running blocks to finish; it flushes any pending work. To achieve the former semantics, you would need to use synchronization primitives in each of your dispatch queue blocks (without a higher level abstraction).