Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Submitting a <code>dispatch_barrier_async</code> to a serial queue is no different than <code>dispatch_async</code> because the queue is serial, so there couldn't be any readers to keep out, since only one block can execute on a serial queue at a time. Put differently, <em>every</em> block is a "barrier block" on a serial queue.</p> <p>If you <code>dispatch_barrier_async</code> to the non-global concurrent queue, then readers will be kept out of THAT queue, but not the global queue it targets. It acts as a barrier only to the queue it is submitted to.</p> <p>If you want to further convince yourself, think of it this way: All queues ultimately target one of the global concurrent queues (background, low, default, and high priority). With that in mind, if <code>dispatch_barrier*</code> to any queue transitively caused a barrier on the global queue that the submitted-to queue ultimately targeted, then it would be trivial to use <code>dispatch_barrier*</code> to starve out all other clients of GCD (by submitting a barrier block to 4 private concurrent queues, each of which targeted a different priority global queue.) That would be totally bogus. </p> <p>Coming at it from the other direction: <code>dispatch_barrier*</code> is useful specifically <em>because</em> you can create arbitrary units of mutual exclusion (i.e. non-global concurrent queues).</p> <p>In short: the queue you submit to is the unit of "protection" (or "barrier-ness").</p> <p><strong>EDIT</strong>: If you're willing to take the above at face value, you can stop reading, but in an effort to give some more clarity here, I coded up a quick example to prove my claims. As some background, this is from <a href="https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_barrier_async" rel="nofollow">Apple's documentation</a>:</p> <blockquote> <p>If the queue you pass to this function [<code>disaptch_barrier_async</code>] is a serial queue or one of the global concurrent queues, this function behaves like the <code>dispatch_async function</code>.</p> </blockquote> <p>This means that a <code>disaptch_barrier_async</code> submitted to a serial queue will have no external effect, nor will a <code>disaptch_barrier_async</code> submitted to a global queue. Rather than merely appeal to authority, I'll prove these two claims.</p> <h2>Barrier block submitted to private serial queue</h2> <p>Here's the code:</p> <pre><code>static void FakeWork(NSString* name, NSTimeInterval duration, dispatch_group_t groupToExit); - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { dispatch_queue_t privateSerialQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL); dispatch_queue_t privateConcurQueue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t globalConcurQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_set_target_queue(privateSerialQueue, privateConcurQueue); dispatch_set_target_queue(privateConcurQueue, globalConcurQueue); // Barrier block submitted to serial queue. Per the docs, we expect this to have no effect // and behave like dispatch_async. So, we expect this to run to completion in 15s. { NSString* testDesc = @"Checking for effects of barrier block on serial queue"; dispatch_suspend(globalConcurQueue); dispatch_group_t group = dispatch_group_create(); NSDate* start = [NSDate date]; NSLog(@"%@\nStarting test run at: %@", testDesc, start); // We expect these to take 15s total dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A1: 5s Job on privateSerialQueue", 5.0, group); }); dispatch_barrier_async(privateSerialQueue, ^{ FakeWork(@"A2: 5s BARRIER Job on privateSerialQueue", 5.0, group); }); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A3: 5s Job on privateSerialQueue", 5.0, group); }); // So we'll make 3 15s jobs each for the privateConcurrentQueue and globalConcurrentQueue dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B1: 15s Job on privateConcurQueue", 15.0, group); }); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B2: 15s Job on privateConcurQueue", 15.0, group); }); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B3: 15s Job on privateConcurQueue", 15.0, group); }); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(globalConcurQueue, ^{ FakeWork(@"C1: 15s Job on globalConcurQueue", 15.0, group); }); dispatch_async(globalConcurQueue, ^{ FakeWork(@"C2: 15s Job on globalConcurQueue", 15.0, group); }); dispatch_async(globalConcurQueue, ^{ FakeWork(@"C3: 15s Job on globalConcurQueue", 15.0, group); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSDate* end = [NSDate date]; NSLog(@"Test run finished at: %@ duration: %@", end, @([end timeIntervalSinceDate: start])); } } static void FakeWork(NSString* name, NSTimeInterval duration, dispatch_group_t groupToExit) { NSDate* start = [NSDate date]; NSLog(@"Starting task: %@ withDuration: %@ at: %@", name, @(duration), start); while (1) @autoreleasepool { NSTimeInterval t = [[NSDate date] timeIntervalSinceDate: start]; if (t &gt;= duration) { break; } else if ((t + 0.0005) &lt; duration) { usleep(50); } } NSDate* end = [NSDate date]; duration = [end timeIntervalSinceDate: start]; NSLog(@"Finished task: %@ withRealDuration: %@ at: %@", name, @(duration), end); if (groupToExit) { dispatch_group_leave(groupToExit); } } </code></pre> <p>If a <code>dispatch_barrier_async</code> had any effect at all on the targeted queue, we would expect this to take more than 15 seconds, but here's the output:</p> <pre><code>Checking for effects of barrier block on serial queue Starting test run at: 2013-09-19 12:16:25 +0000 Starting task: C1: 15s Job on globalConcurQueue withDuration: 15 at: 2013-09-19 12:16:25 +0000 Starting task: C2: 15s Job on globalConcurQueue withDuration: 15 at: 2013-09-19 12:16:25 +0000 Starting task: C3: 15s Job on globalConcurQueue withDuration: 15 at: 2013-09-19 12:16:25 +0000 Starting task: A1: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:16:25 +0000 Starting task: B1: 15s Job on privateConcurQueue withDuration: 15 at: 2013-09-19 12:16:25 +0000 Starting task: B2: 15s Job on privateConcurQueue withDuration: 15 at: 2013-09-19 12:16:25 +0000 Starting task: B3: 15s Job on privateConcurQueue withDuration: 15 at: 2013-09-19 12:16:25 +0000 Finished task: A1: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:16:30 +0000 Starting task: A2: 5s BARRIER Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:16:30 +0000 Finished task: A2: 5s BARRIER Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:16:35 +0000 Starting task: A3: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:16:35 +0000 Finished task: C1: 15s Job on globalConcurQueue withRealDuration: 15.00000900030136 at: 2013-09-19 12:16:40 +0000 Finished task: C2: 15s Job on globalConcurQueue withRealDuration: 15 at: 2013-09-19 12:16:40 +0000 Finished task: C3: 15s Job on globalConcurQueue withRealDuration: 15 at: 2013-09-19 12:16:40 +0000 Finished task: B1: 15s Job on privateConcurQueue withRealDuration: 15 at: 2013-09-19 12:16:40 +0000 Finished task: B2: 15s Job on privateConcurQueue withRealDuration: 15 at: 2013-09-19 12:16:40 +0000 Finished task: A3: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:16:40 +0000 Finished task: B3: 15s Job on privateConcurQueue withRealDuration: 15 at: 2013-09-19 12:16:40 +0000 Test run finished at: 2013-09-19 12:16:40 +0000 duration: 15.00732499361038 </code></pre> <h2>Barrier block submitted to global concurrent queue</h2> <p>Let's also verify the point from the documentation that barrier blocks submitted to the global concurrent queue have no barrier effect. Here's some code (just the differences from the first example):</p> <pre><code> { NSString* testDesc = @"Barrier block submitted to globalConcurQueue"; dispatch_group_t group = dispatch_group_create(); NSDate* start = [NSDate date]; NSLog(@"%@\nStarting test run at: %@", testDesc, start); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A1: 5s Job on privateSerialQueue", 5.0, group); }); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A2: 5s Job on privateSerialQueue", 5.0, group); }); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A3: 5s Job on privateSerialQueue", 5.0, group); }); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B1: 15s Job on privateConcurQueue", 15.0, group); }); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B2: 15s Job on privateConcurQueue", 15.0, group); }); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B3: 15s Job on privateConcurQueue", 15.0, group); }); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(globalConcurQueue, ^{ FakeWork(@"C1: 15s Job on globalConcurQueue", 15.0, group); }); dispatch_barrier_async(globalConcurQueue, ^{ FakeWork(@"C2: 15s BARRIER Job on globalConcurQueue", 15.0, group); }); dispatch_async(globalConcurQueue, ^{ FakeWork(@"C3: 15s Job on globalConcurQueue", 15.0, group); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSDate* end = [NSDate date]; NSLog(@"Test run finished at: %@ duration: %@", end, @([end timeIntervalSinceDate: start])); } </code></pre> <p>If the barrier block submitted to the global concurrent queue had any effect, we would expect this to take longer than 15s, but here's the output:</p> <pre><code>Barrier block submitted to globalConcurQueue Starting test run at: 2013-09-19 12:33:28 +0000 Starting task: C1: 15s Job on globalConcurQueue withDuration: 15 at: 2013-09-19 12:33:28 +0000 Starting task: C2: 15s BARRIER Job on globalConcurQueue withDuration: 15 at: 2013-09-19 12:33:28 +0000 Starting task: C3: 15s Job on globalConcurQueue withDuration: 15 at: 2013-09-19 12:33:28 +0000 Starting task: B1: 15s Job on privateConcurQueue withDuration: 15 at: 2013-09-19 12:33:28 +0000 Starting task: A1: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:33:28 +0000 Starting task: B2: 15s Job on privateConcurQueue withDuration: 15 at: 2013-09-19 12:33:28 +0000 Starting task: B3: 15s Job on privateConcurQueue withDuration: 15 at: 2013-09-19 12:33:28 +0000 Finished task: A1: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:33:33 +0000 Starting task: A2: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:33:33 +0000 Finished task: A2: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:33:38 +0000 Starting task: A3: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:33:38 +0000 Finished task: C1: 15s Job on globalConcurQueue withRealDuration: 15 at: 2013-09-19 12:33:43 +0000 Finished task: C2: 15s BARRIER Job on globalConcurQueue withRealDuration: 15 at: 2013-09-19 12:33:43 +0000 Finished task: C3: 15s Job on globalConcurQueue withRealDuration: 15 at: 2013-09-19 12:33:43 +0000 Finished task: B2: 15s Job on privateConcurQueue withRealDuration: 15 at: 2013-09-19 12:33:43 +0000 Finished task: B3: 15s Job on privateConcurQueue withRealDuration: 15 at: 2013-09-19 12:33:43 +0000 Finished task: B1: 15s Job on privateConcurQueue withRealDuration: 15 at: 2013-09-19 12:33:43 +0000 Finished task: A3: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:33:43 +0000 Test run finished at: 2013-09-19 12:33:43 +0000 duration: 15.00729995965958 </code></pre> <h2>Barrier block submitted to private concurrent queue</h2> <p>The next thing to test is the effects of barrier blocks submitted to the private concurrent queue. Since the serial queue targets the private concurrent queue, I expect blocks submitted to the serial queue to be held up for barrier blocks submitted to the private concurrent queue. And indeed, that's the case. Here's the code:</p> <pre><code> // Barrier block submitted to private concurrent queue. { NSString* testDesc = @"Checking for effects of barrier block on private concurrent queue"; dispatch_suspend(globalConcurQueue); dispatch_group_t group = dispatch_group_create(); NSDate* start = [NSDate date]; NSLog(@"%@\nStarting test run at: %@", testDesc, start); // Make 3 5s jobs on the private concurrent queue and make the middle one a barrier, which should serialize them dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A1: 5s Job on privateSerialQueue", 5.0, group); }); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B1: 5s Job on privateConcurQueue", 5.0, group); }); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A2: 5s Job on privateSerialQueue", 5.0, group); }); dispatch_barrier_async(privateConcurQueue, ^{ FakeWork(@"B2: 5s BARRIER Job on privateConcurQueue", 5.0, group); }); dispatch_async(privateSerialQueue, ^{ FakeWork(@"A3: 5s Job on privateSerialQueue", 5.0, group); }); dispatch_async(privateConcurQueue, ^{ FakeWork(@"B3: 5s Job on privateConcurQueue", 5.0, group); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSDate* end = [NSDate date]; NSLog(@"Test run finished at: %@ duration: %@", end, @([end timeIntervalSinceDate: start])); } </code></pre> <p>And here's the output:</p> <pre><code>Checking for effects of barrier block on private concurrent queue Starting test run at: 2013-09-19 12:24:17 +0000 Starting task: B1: 5s Job on privateConcurQueue withDuration: 5 at: 2013-09-19 12:24:17 +0000 Starting task: A1: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:24:17 +0000 Finished task: A1: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:24:22 +0000 Finished task: B1: 5s Job on privateConcurQueue withRealDuration: 5 at: 2013-09-19 12:24:22 +0000 Starting task: A2: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:24:22 +0000 Finished task: A2: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:24:27 +0000 Starting task: A3: 5s Job on privateSerialQueue withDuration: 5 at: 2013-09-19 12:24:27 +0000 Finished task: A3: 5s Job on privateSerialQueue withRealDuration: 5 at: 2013-09-19 12:24:32 +0000 Starting task: B2: 5s BARRIER Job on privateConcurQueue withDuration: 5 at: 2013-09-19 12:24:32 +0000 Finished task: B2: 5s BARRIER Job on privateConcurQueue withRealDuration: 5 at: 2013-09-19 12:24:37 +0000 Starting task: B3: 5s Job on privateConcurQueue withDuration: 5 at: 2013-09-19 12:24:37 +0000 Finished task: B3: 5s Job on privateConcurQueue withRealDuration: 5 at: 2013-09-19 12:24:42 +0000 Test run finished at: 2013-09-19 12:24:42 +0000 duration: 25.00404000282288 </code></pre> <p>Not surprisingly, when the barrier block is executing, it is the only block submitted to either queue that is executing. That's because the "unit of protection" is the private concurrent queue of which the private serial queue is a "sub-unit" of. The curious thing that we see here is that task A3, which was submitted to the private serial queue AFTER task B2 was submitted to the private concurrent queue, executes <em>before</em> B2. I'm not sure why that is, but the fundamental unit of protection (i.e. the private concurrent queue) was not violated. Based on that, I conclude that you can't count on the ordering of tasks submitted to two different queues, even if you happen to know that one queue targets the other.</p> <p>So there you have it. We've proved that <code>dispatch_barrier_async</code> is the same as <code>dispatch_sync</code> on serial and global concurrent queues, just like the documentation said it would be, which left only one operation left to test (a <code>dispatch_barrier_async</code> to the private concurrent queue), and we've illustrated that the unit of protection is preserved in that case <em>including</em> operations submitted to other private queues that target it.</p> <p>If there's some case you're still not clear on, please comment.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload