2

我正在尝试了解 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. 为什么第三个循环不能作为第一个循环运行而不会被其他任务中断?

4

1 回答 1

4

您问:

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

那是因为你用过dispatch_sync. 这实际上是说“停止当前线程继续执行,直到分派的任务完成。” 因此,第一个循环甚至不会进入它自己循环的下一次迭代,直到之前调度的任务dispatch_sync完成。

如果你看下图,dispatch_sync调用是红色的Ⓢ标志。您可以看到,在第一个分派的任务完成之前,它甚至不会分派第一个循环的第二次迭代。

  1. 为什么第三个循环比第二个循环更早开始?

这是一个经典的比赛条件。您正在将大量任务分派到并发队列(所有全局队列都是并发队列),从技术上讲,它按照它们排队的顺序启动它们,但是由于它们被允许同时运行,它们运行在同时,您无法保证哪个将NSLog首先达到其各自的声明。如果您查看与这些NSLog语句关联的时间戳,它们彼此非常接近(与NSLog第一个循环的语句不同)。

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

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

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

    这是一个我建议您不要担心的技术细节,但它确实解释了为什么您经常会看到任务启动比在大致相同的时间在同一个并发队列上dispatch_sync启动更快。dispatch_async

因此,在下图中,迭代 3-5(第二次循环)和迭代 6(第三次循环的第一次迭代)的调度调用(红色Ⓢ)发生得非常接近,以至于标志相互叠加。但是您可以在图表下方的列表中看到这些时间。

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

问题不在于第一个循环“没有中断”地运行,而仅仅是队列上没有其他任何东西在运行,并且因为它是同步运行的,所以在循环 1 完成之前没有其他东西会在它上面启动。而第三个循环几乎在与第二个循环的所有迭代 #3 到 5 相同的时间分派了迭代 #6。

我认为查看这九个已调度任务的时间线(由 Instruments 的“兴趣点”工具生成)是说明性的:

在此处输入图像描述

前三个浅蓝色任务代表第一个循环。紫色任务是第二个循环。橙色任务是第三个循环。和调用dispatch_syncdispatch_async红色 Ⓢ 标志表示。

如您所见,第一个和第三个循环表现出相同的行为,即因为您正在同步调度这些块,它甚至无法尝试调度下一个任务,直到先前同步调度的任务完成运行。但是第二个循环运行得非常快,一个接一个地分派所有三个任务,非常快,并且这些任务彼此并发运行,而主线程继续分派第三个循环,而第二个循环分派的任务还在运行。

于 2018-03-28T16:08:27.887 回答