Skip to content

Instantly share code, notes, and snippets.

@jspahrsummers
Last active January 20, 2021 11:55
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jspahrsummers/dbd861d425d783bd2e5a to your computer and use it in GitHub Desktop.
Save jspahrsummers/dbd861d425d783bd2e5a to your computer and use it in GitHub Desktop.
Synchronizing with multiple GCD queues
//
// DON'T do this, or else you risk a deadlock (e.g., by accidentally performing it in a different order somewhere)
//
dispatch_async(firstQueue, ^{
dispatch_sync(secondQueue, ^{
// code requiring both queues
});
});
//
// DO this instead
//
dispatch_queue_t concurrentQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(firstQueue, concurrentQueue);
dispatch_set_target_queue(secondQueue, concurrentQueue);
dispatch_barrier_async(concurrentQueue, ^{
// code requiring both queues
});
@qnoid
Copy link

qnoid commented Mar 2, 2016

Can you please clarify how the dispatch_set_target_queue affects dispatch_barrier_async?
From the GCD doc it's not clear.

Discussion
An object's target queue is responsible for processing the object. The target queue determines the queue on which the object's finalizer is invoked. In addition, modifying the target queue of some objects changes their behavior:

Dispatch queues:

A dispatch queue's priority is inherited from its target queue. Use the dispatch_get_global_queue function to obtain a suitable target queue of the desired priority.

If you submit a block to a serial queue, and the serial queue’s target queue is a different serial queue, that block will not be invoked concurrently with blocks submitted to the target queue or to any other queue with that same target queue.

IMPORTANT
If you modify the target queue for a queue, you must be careful to avoid creating cycles in the queue hierarchy.

@jspahrsummers
Copy link
Author

@qnoid dispatch_set_target_queue makes every block executed from firstQueue or secondQueue behave "as if" it were submitted to concurrentQueue using dispatch_async.

Being a target queue has no effect on the semantics of dispatch_barrier_* functions. Barriers guarantee that nothing else on the queue is executing while the barrier block is running.

@qnoid
Copy link

qnoid commented Mar 2, 2016

@jspahrsummers thanks!

@mlaffitteIntego
Copy link

mlaffitteIntego commented Sep 27, 2017

@jspahrsummers
Hi, I have concerns about blocks already/still running for instance on firstQueue when dispatch_set_target_queue(firstQueue, concurrentQueue) is called. I suppose that after the call to dispatch_set_target_queue() they continue running on the original target queue. Hence, I fear that a dispatch_barrier on concurrentQueue used as the new target queue will not wait for those blocks running on their original target queues to complete before starting the barrier block.
What do you think about this ?
Marc

@mlaffitteIntego
Copy link

mlaffitteIntego commented Sep 28, 2017

I have run tests and my fear has been confirmed: Execution of the block added via dispatch_barrier may start before blocks added to both queues complete if those blocks have been added before the call to dispatch_set_target.
A solution ensuring that nothing runs anymore on both queues when the barrier block starts is to use nested barriers:

dispatch_barrier_async(firstQueue, ^{
	dispatch_barrier_sync(secondQueue, ^{
		// barrier block
		// when it is called, we are ensured that nothing is running anymore on both queues
	});
});

Note that even if the outer barrier can be asynch, the inner barriers should be synch.
This solution has also the advantage not to mess with the original queue targets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment