为什么并发队列的奇怪行为?

时间:2018-03-28 15:20:23

标签: ios grand-central-dispatch concurrent-queue

我正在尝试了解iOS GCD的并发队列。 我做了一些代码来测试它,但发现了一些奇怪的东西。 代码如下:

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

for (int index = 0; index < 3; ++index) {
    dispatch_sync(_syncQueue, ^{

        NSLog(@"sync@@@@@@ >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync@@@@@@ <<<<    %d ",index);
    });
}

for (int index = 3; index < 6; ++index) {
    dispatch_async(_syncQueue, ^{

        NSLog(@"sync===== >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync===== <<<<    %d ",index);
    });
}

for (int index = 6; index < 9; ++index) {
    dispatch_sync(_syncQueue, ^{

        NSLog(@"sync***** >>>>    %d ",index);
        sleep(1);
        NSLog(@"sync***** <<<<    %d ",index);
    });
}

执行结果如下:

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

我很困惑,也无法理解为什么会这样。

  1. 为什么直到第一个循环完全运行然后第二个和第三个循环才能开始运行,对我来说,第一个循环应该至少可以被第二个循环中断,因为第二个循环可以创建新的线程来执行。

  2. 为什么第3个循环比第2个循环更早开始?

  3. 为什么第3个循环无法作为第1个循环运行而不会被其他任务中断?

1 个答案:

答案 0 :(得分:4)

你问:

  
      
  1. 为什么直到第一个循环完全运行然后第二个和第三个循环才能开始运行,对我来说,第一个循环应该至少可以被第二个循环中断,因为第二个循环可以创建新的线程来执行。
  2.   

那是因为您使用了dispatch_sync。这实际上说&#34;停止当前线程继续进行,直到调度任务完成。&#34;因此,第一个循环甚至不会进入其自身循环的下一次迭代,直到先前dispatch_sync调度完成的任务完成。

如果你看下面的图表,dispatch_sync电话是红色Ⓢ标志。您可以看到,它甚至无法调度第一个循环的第二次迭代,直到第一个调度任务完成为止。

  
      
  1. 为什么第3个循环比第2个循环更早开始?
  2.   

这是一场经典的比赛。您将大量任务分派到并发队列(所有全局队列都是并发队列),从技术上讲,它们按照排队的顺序启动它们,但由于它们可以同时运行,因此它们和#39;同时重新运行,并且您无法保证哪个实际上会首先到达其各自的NSLog语句。如果您查看与这些NSLog语句关联的时间戳彼此非常接近(与第一个循环的NSLog语句不同)。

请注意,虽然您在技术上无法保证第二个或第三个循环调度的任务是否会首先启动,但有两个有趣的细节:

  1. 我们可以相对确信第三个循环的后续迭代(即迭代7和8)将不会在第二个循环的调度任务之前启动,因为再次,您在第三个循环中调度所有内容同步。因此,例如,它甚至不会尝试调度迭代7,直到完成迭代6的执行(而第二个循环已经异步调度其任务,并且那些任务将在该并发队列上不加减少)。

  2. 请注意,虽然您无法保证第二个循环调度的任务的时间安排以及第三个循环调度的第一个任务,但实际上,您通常会看到第三个循环的第一个任务开始更多很快,因为内置于dispatch_sync的优化。第二个循环使用的dispatch_async必须做很多工作,即GCD必须从池中获取工作线程并在该线程上启动任务。但是作为优化的第三个循环的dispatch_sync通常只运行当前线程上的调度任务。 (如果线程必须等待调度的任务,为什么不使用它来运行任务并完全避免上下文切换。)

    这是我建议您不要担心的技术细节,但确实解释了为什么您经常看到dispatch_sync任务的启动速度比dispatch_async更快几乎在同一时间开始在同一个并发队列中。

  3. 因此,在下图中,迭代3-5(第二个循环)和迭代6(第三个循环的第一次迭代)的调度调用(红色Ⓢ)发生得如此接近,以至于标志叠加在顶部彼此。但是你可以看到图表下方列表中的那些时间。

      
        
    1. 为什么第3个循环不能作为第1个循环运行而不会被其他任务中断?
    2.   

    问题不在于第一次循环运行&#34;没有中断&#34;但仅仅是因为队列中没有其他任何东西在运行,并且由于它是同步运行的,所以在循环1完成之前没有其他任何东西可以启动。而第三个循环在与第二个循环#3到5的所有迭代几乎同时发送迭代#6。

    我认为这些9个调度任务的时间表(由乐器和#34;兴趣点&#34;工具制作)的说明是

    enter image description here

    前三个淡蓝色任务代表第一个循环。紫色任务是第二个循环。橙色任务是第三个循环。 dispatch_syncdispatch_async来电显示为红色Ⓢ标志。

    正如您所看到的,第一个和第三个循环显示相同的行为,即因为您同步调度这些块,它甚至不能尝试分派下一个任务,直到先前的同步调度任务完成运行。但是第二个循环运行速度非常快,一个接一个地发送所有三个任务,非常快,并且这些任务相互之间同时运行,同时主线程继续调度第三个循环,而第二个循环调度任务还在跑步。