为什么在并发队列上屏障的奇怪行为?

时间:2018-04-01 16:02:38

标签: objective-c grand-central-dispatch

我正在尝试验证dispatch_barrier_async的功能,并使用“兴趣点”来检查发生了什么。

代码如下:

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for (int index = 0; index < 3; ++index) {
    kdebug_signpost_start(0, 0, 0, 0, 0);
    dispatch_sync(_syncQueue, ^{
        NSLog(@"sync@@@@@@ >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync@@@@@@ <<<<    %d ",index);

    });
    kdebug_signpost_end(0, 0, 0, 0, 0);
}

for (int index = 3; index < 6; ++index) {
    kdebug_signpost_start(1, 0, 0, 0, 1);
    dispatch_barrier_async(_syncQueue, ^{
        NSLog(@"sync===== >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync===== <<<<    %d ",index);

    });
    kdebug_signpost_end(1, 0, 0, 0, 1);
}

for (int index = 6; index < 9; ++index) {
    kdebug_signpost_start(2, 0, 0, 0, 2);
    dispatch_sync(_syncQueue, ^{
        NSLog(@"sync***** >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync***** <<<<    %d ",index);
    });
    kdebug_signpost_end(2, 0, 0, 0, 2);
}

我希望

  • dispatch_barrier_async调度的所有任务(第二个循环)应该在执行完成的任务完成后工作;以及

  • 一旦dispatch_barrier_async调度的任务开始工作,就无法运行同一队列中的其他任务。

但结果如下:

sync@@@@@@ >>>>    0
sync@@@@@@ <<<<    0
sync@@@@@@ >>>>    1
sync@@@@@@ <<<<    1
sync@@@@@@ >>>>    2
sync@@@@@@ <<<<    2
sync***** >>>>    6
sync===== >>>>    3
sync===== >>>>    4
sync===== >>>>    5
sync***** <<<<    6
sync***** >>>>    7
sync===== <<<<    3
sync===== <<<<    4
sync===== <<<<    5
sync***** <<<<    7
sync***** >>>>    8

我的困惑如下:

  1. 由于任务6已经在任务3之前启动,而任务3是dispatch_barrier_async调度任务,为什么taks 3不等待任务6完成然后启动?这违反了我的期望a);

  2. 在dispatch_barrier_async调度任务5开始工作后,在任务5完成之前启动另一个任务7,这违反了我的期望b)

    我使用“兴趣点”工具来调试它,但似乎第二个循环没有显示在由仪器生成的图形上。这是我第一次使用这个工具,有人可以帮助指出这里的问题是什么吗?

    enter image description here

    [04/02] 谢谢Rob。按照你的建议后,这里有一些更新。

    UPDATE1: 将第二个循环的队列创建方法更改为dispatch_queue_create后,效果很好。 dispatch_barrier_async调度的任务将等待它们完成之前的任务。并且他们之后的任务将启动它们(由dispatch_barrier_async调度的任务)完成。

    enter image description here

    UPDATE2: 然后我使用了你提供的代码,并将第二个循环的队列创建更改回dispatch_async,它们也可以作为期望。 但是,“兴趣点”图形存在一些问题。第二个循环中的某些任务无法正确显示。

    1. Xcode指示(0x4 0x0 0x0 0x1)和(0x5 0x0 0x0 0x1)的某些错误,因为“无法将间隔记录为间隔,因为无法确定启动。”

      < / LI>
    2. 未列出(0x3 0x0 0x0 0x1),但(0x5 0x0 0x0 0x1)列出两次。并且图形上只显示一个(0x5 0x0 0x0 0x1)

    3. 我从未在“兴趣点”工具中看到指标。。

    4. 这会产生以下信息:

        

      00:00.000.000 2ndLoop(KDebug Interval Signpost)以test.app(pid:64903,tid:0x1619027)结束,带参数:(0x4 0x0 0x0 0x1)。间隔不能记录为间隔,因为无法确定启动。 __kdebug_trace64←(8其他帧)

           

      00:00.000.000 2ndLoop(KDebug Interval Signpost)以test.app(pid:64903,tid:0x1619028)结束,带参数:(0x5 0x0 0x0 0x1)。间隔不能记录为间隔,因为无法确定启动。 __kdebug_trace64←(8其他帧)

           

      00:04.134.364 2ndLoop(KDebug Interval Signpost),由test.app(pid:64903,tid:0x1619028)启动,带参数:(0x5 0x0 0x0 0x1),1.00 s后由test.app结束(pid :64903,tid:0x1619026),带参数:(0x3 0x0 0x0 0x1)__ kdebug_trace64←(9其他帧)

      enter image description here

1 个答案:

答案 0 :(得分:2)

有两个不同的问题:

  1. 您不能对全局队列使用障碍。屏障说“当这个调度的任务正在运行时,不要在这个队列上运行任何东西”,所以当你尝试在全局队列上执行操作时,操作系统显然不喜欢它。正如the documentation所说,它只会对自定义并发队列而不是全局队列构成障碍:

      

    您指定的队列应该是您使用dispatch_queue_create函数自己创建的并发队列。如果传递给此函数的队列是串行队列或全局并发队列之一,则此函数的行为类似于dispatch_async函数

    创建自己的并发队列,它应该按预期工作。

    dispatch_queue_t queue = dispatch_queue_create("com.domain.app.test", DISPATCH_QUEUE_CONCURRENT);
    

    产量:

    enter image description here

  2. 您的“兴趣点”图表显示了调度过程(即主线程正在占用多少时间),而不是任务的运行本身。所以在你的第二次循环迭代中,它们的异步调度每次只需要25.54,5.17和4.72μs(微秒)。这就是为什么你没有在“兴趣点”工具中以图形方式看到你的第二次迭代。它们发生得太快而无法用图形表示。 (顺便说一句,这是一件非常好的事情,也就是我们从不同步调度慢任务的原因;我们绝不想阻止主线程。)

    通常,在诊断GCD行为时,我发现“兴趣点”区域对于表示任务的运行更有用,而不是任务的调度。

    要实现这一点,将kdebug语句放在块中可能更有用,从而产生上图。因此:

    dispatch_queue_t queue = dispatch_queue_create("com.domain.app.test", DISPATCH_QUEUE_CONCURRENT);
    
    for (int index = 0; index < 3; ++index) {
        dispatch_sync(queue, ^{
            kdebug_signpost_start(0, index, 0, 0, 0);
            sleep(1);
            kdebug_signpost_end(0, index, 0, 0, 0);
        });
    }
    
    for (int index = 3; index < 6; ++index) {
        dispatch_barrier_async(queue, ^{
            kdebug_signpost_start(1, index, 0, 0, 1);
            sleep(1);
            kdebug_signpost_end(1, index, 0, 0, 1);
        });
    }
    
    for (int index = 6; index < 9; ++index) {
        dispatch_sync(queue, ^{
            kdebug_signpost_start(2, index, 0, 0, 2);
            sleep(1);
            kdebug_signpost_end(2, index, 0, 0, 2);
        });
    }
    
  3. 在修改后的问题中,您要问:

      

    然后我使用了您提供的代码,并将第二个循环的队列创建更改回dispatch_async,它们也可以作为期望。但是,“兴趣点”图形存在一些问题。第二个循环中的某些任务无法正确显示。

         
        
    1. Xcode指示(0x4 0x0 0x0 0x1)和(0x5 0x0 0x0 0x1)的某些错误,因为“无法将间隔记录为间隔,因为无法确定启动。”

      < / LI>   
    2. 未列出(0x3 0x0 0x0 0x1),但(0x5 0x0 0x0 0x1)列出两次。并且图形上只显示一个(0x5 0x0 0x0 0x1)

    3.   

    “兴趣点”的诀窍在于,在执行GCD时,它会重用工作线程,因此如何匹配开始和结束kdebug调用会产生混淆。如果您注意到,在我的示例中,我将循环编号作为“code”参数传递给kdebug调用,迭代编号作为之后的第一个参数。然后我转到乐器中的“录音选项”并通过(a)代码告诉它匹配开始/结束调用; (b)代码后面的第一个参数:

    enter image description here

    你继续问:

      
        
    1. 我从未在“兴趣点”工具中看到指标。。
    2.   

    好吧,如果你想看到这些内容,你必须发帖,请看下面的kdebug_signpost来电(而不是之前显示的kdebug_signpost_startkdebug_signpost_end来电),例如:

    dispatch_queue_t queue = dispatch_queue_create("com.domain.app.test", DISPATCH_QUEUE_CONCURRENT);
    
    for (int index = 0; index < 3; ++index) {
        kdebug_signpost(0, index, 0, 0, 0);
        dispatch_sync(queue, ^{
            kdebug_signpost_start(0, index, 0, 0, 0);
            sleep(1);
            kdebug_signpost_end(0, index, 0, 0, 0);
        });
    }
    
    for (int index = 3; index < 6; ++index) {
        kdebug_signpost(1, index, 0, 0, 0);
        dispatch_barrier_async(queue, ^{
            kdebug_signpost_start(1, index, 0, 0, 1);
            sleep(1);
            kdebug_signpost_end(1, index, 0, 0, 1);
        });
    }
    
    for (int index = 6; index < 9; ++index) {
        kdebug_signpost(2, index, 0, 0, 0);
        dispatch_sync(queue, ^{
            kdebug_signpost_start(2, index, 0, 0, 2);
            sleep(1);
            kdebug_signpost_end(2, index, 0, 0, 2);
        });
    }
    

    产量:

    enter image description here

    再次注意,红色Ⓢ标志表示我从主线程发出调度电话时,彩色区域表示任务的时间安排。

    我建议你观看WWDC 2016视频System Trace in Depth,了解有关如何使用“兴趣点”工具的详细信息。