A while back I wrote a pseudo-category for GCD and posted it to Github. It contains several convenience functions. One of them was a method that makes synchronous calls “safe,” or so I thought. The code looked like this:
void dispatch_sync_safe(dispatch_queue_t queue, dispatch_block_t block) {
// If we're already on the given queue, just run the block.
if (dispatch_get_current_queue() == queue)
block();
// Otherwise, dispatch to the given queue.
else
dispatch_sync(queue, block);
}
The idea is that when doing a synchronous dispatch to a queue, I check if the program was already running on that queue. If it is, then the block is immediately executed. Otherwise, the block is run with a regular dispatch_sync. In my naïve implementation I thought this would prevent deadlock. The idea is nice but not correct. Consider the case when we dispatch_sync_safe to queue A. Next, we dispatch_sync_safe to queue B. Finally, we dispatch_sync_safe back to queue A. This is guaranteed deadlock.
As a result of this case, I immediately marked dispatch_sync_safe as deprecated in my Github repo. Anyone using this function thinking it is perfectly safe should think again. I did, however, add another function called dispatch_sync_checked. It is actually the same implementation as dispatch_sync_safe just under a different name. I recognize that this simple function has great value, but the old name was misleading. The moral of the story is, use dispatch_async (or one of my dispatch_async convenience methods) whenever possible. If you must run code synchronously, use dispatch_sync_checked and be very cautious of deadlock.
As I mentioned previously, dispatch_sync_checked still has great value. There is at least one case where you can guarantee you won’t deadlock. Consider the case when you have an object with a private serial dispatch queue. This queue is used to serialize access to some shared memory within the object. Using dispatch_sync_checked on the private queue allows you to keep your object’s methods synchronous and thread safe. This requires following two conditions in your critical code:
- Do not make synchronous calls to external objects (which could synchronously call the calling object again).
- Do not call dispatch_sync on any queue (or dispatch_sync_checked on any queue except the private queue).