1

我有一个关于dispatch_barrier目标队列的问题。我有一个自定义串行队列和自定义并发队列,我将串行队列的目标队列设置为并发队列,然后以全局并发队列为目标:

(serial queue) -> (concurrent queue) -> (global concurrent queue)

当我dispatch_barrier阻塞串行队列时会发生什么?它会阻止提交到并发队列的块的执行,还是只阻止串行队列中的执行块?或者如果我dispatch_barrier阻塞到非全局并发队列,它是否也会阻塞提交到串行队列的块的执行,或者只执行非全局并发队列中的块?

感谢您的关注。:)

4

1 回答 1

6

将 a提交dispatch_barrier_async到串行队列没有什么不同,dispatch_async因为队列是串行的,因此无法阻止任何读者,因为一次只能在串行队列上执行一个块。换句话说,每个块都是串行队列上的“障碍块”。

如果您dispatch_barrier_async使用非全局并发队列,那么读者将被排除在该队列之外,而不是它所针对的全局队列。它仅作为它提交到的队列的屏障。

如果你想进一步说服自己,可以这样想:所有队列最终都针对全局并发队列之一(后台、低、默认和高优先级)。考虑到这一点,如果dispatch_barrier*对任何队列传递性地在全局队列上造成了提交到队列最终目标的屏障,那么使用dispatch_barrier*来饿死 GCD 的所有其他客户端将是微不足道的(通过向 4 个私有提交屏障块并发队列,每个队列都针对不同的优先级全局队列。)那完全是假的。

从另一个方向来看:dispatch_barrier*特别有用,因为您可以创建任意互斥单元(即非全局并发队列)。

简而言之:您提交的队列是“保护”(或“障碍”)的单位。

编辑:如果您愿意从表面上看上述内容,您可以停止阅读,但为了在这里更清楚,我编写了一个快速示例来证明我的主张。作为一些背景,这是来自Apple 的文档

如果您传递给此函数 [ disaptch_barrier_async] 的队列是串行队列或全局并发队列之一,则此函数的行为类似于dispatch_async function.

这意味着disaptch_barrier_async提交到串行队列将没有外部影响,也不会disaptch_barrier_async提交到全局队列。我将证明这两个主张,而不仅仅是诉诸权威。

屏障块提交到私有串行队列

这是代码:

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 >= duration)
        {
            break;
        }
        else if ((t + 0.0005) < 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);
    }
}

如果 adispatch_barrier_async对目标队列有任何影响,我们预计这将花费 15 秒以上,但输出如下:

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

提交到全局并发队列的屏障

让我们也验证一下文档中提交给全局并发队列的屏障块没有屏障效果的观点。这是一些代码(只是与第一个示例的区别):

    {
        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]));
    }

如果提交到全局并发队列的屏障块有任何影响,我们预计这将花费超过 15 秒的时间,但输出如下:

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

屏障块提交到私有并发队列

接下来要测试的是提交到私有并发队列的屏障块的效果。由于串行队列以私有并发队列为目标,因此我希望提交给串行队列的块会被提交给私有并发队列的屏障块保留。确实如此。这是代码:

    // 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]));
    }

这是输出:

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

毫不奇怪,当屏障块正在执行时,它是提交给正在执行的任一队列的唯一块。这是因为“保护单元”是私有串行队列是其“子单元”的私有并发队列。我们在这里看到的奇怪的是,在任务 B2 提交到私有并发队列之后提交到​​私有串行队列的任务 A3在 B2之前执行。我不确定为什么会这样,但基本的保护单元(即私有并发队列)没有被违反。基于此,我得出结论,即使您碰巧知道一个队列以另一个队列为目标,您也不能指望提交到两个不同队列的任务的顺序。

所以你有它。我们已经证明这dispatch_barrier_asyncdispatch_sync串行和全局并发队列相同,就像文档所说的那样,只剩下一个操作要测试(dispatch_barrier_async私有并发队列),我们已经说明了该单元在这种情况下,保护的保护被保留,包括提交给以它为目标的其他私有队列的操作。

如果还有什么不清楚的地方,欢迎评论。

于 2013-09-18T19:32:46.790 回答