为什么并发队列有奇怪的行为?
Why weird behavior of 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
我很困惑,不明白为什么会这样运行。
为什么直到第一个循环 运行 完全完成然后第二个和第三个循环才能开始 运行,对我来说,第一个循环至少应该可以被第二个循环打断,因为第二个循环可以创建新的线程来执行。
为什么第三个循环比第二个循环开始得早?
为什么第 3 个循环不能 运行 作为 运行 没有被其他任务打断的第一个循环?
你问过:
- why until 1st loop runs completely finished then 2nd and 3rd loop can start to run, to me, 1st loop should can be interrupted by 2nd loop at least, since 2nd loop can create new threads to execute.
那是因为您使用了 dispatch_sync
。这实际上是说“停止当前线程继续执行,直到分派的任务完成”。因此,在使用先前 dispatch_sync
分派的任务完成之前,第一个循环甚至不会进入其自身循环的下一次迭代。
如果您查看下图,dispatch_sync
调用是红色 Ⓢ 标志。您可以看到,在第一个分派的任务完成之前,它甚至不会分派第一个循环的第二次迭代。
- Why the 3rd loop start earlier than the 2nd loop ?
这是一个经典的比赛条件。您正在将大量任务分派到并发队列(所有全局队列都是并发队列),从技术上讲,这会按照它们排队的顺序启动它们,但是由于允许它们并发 运行,它们同时 运行 并且您无法保证 运行ces 哪个会首先到达其各自的 NSLog
语句。如果您查看与这些 NSLog
语句关联的时间戳,它们彼此非常接近(与第一个循环的 NSLog
语句不同)。
请注意,虽然从技术上讲您无法确定运行第二个或第三个循环分派的任务是否会首先开始,但有两个有趣的细节:
我们可以比较确定第三个循环的后续迭代(即迭代 7 和 8)不会在第二个循环的调度任务之前开始,因为同样,您在第三个循环中调度所有内容同步地。因此,例如,它甚至不会尝试调度迭代 7,直到迭代 6 的执行完成(而第二个循环已经异步调度其任务,并且这些任务将 运行 在该并发队列上有增无减)。
请注意,虽然您无法确定运行第二个循环分派的任务和第三个循环分派的第一个任务的时间安排,但在实践中,您通常会看到由于 dispatch_sync
的内置优化,第三个循环的第一个任务启动得更快。第二个循环使用的 dispatch_async
必须做很多工作,即 GCD 必须从池中获取一个工作线程,并在该线程上启动任务。但是第三次循环的dispatch_sync
,作为一种优化,往往只是运行s当前线程上派发的任务。 (如果线程无论如何都必须等待分派的任务,为什么不直接使用它来 运行 任务并完全避免上下文切换。)
这是一个技术细节,我建议您不要担心,但确实解释了为什么您经常会看到 dispatch_sync
任务比 dispatch_async
启动得更快并发队列大致在同一时间。
因此,在下图中,迭代 3-5(第二个循环)和迭代 6(第三个循环的第一次迭代)的分派调用(红色 Ⓢ)发生得非常接近,以至于标志叠加在彼此之上。但是你可以在图表下方的列表中看到它们的时间。
- Why 3rd loop cannot run as the 1st loop which runs without interrupted by other tasks ?
问题不在于第一个循环 运行“没有中断”,而仅仅是队列中没有其他 运行ning,因为它是 运行ning同步地,在循环 1 完成之前,没有其他任何事情会开始。而第三个循环几乎与第二个循环的所有迭代 #3 到 5 几乎同时调度迭代 #6。
我认为查看这九个分派任务的时间线(由 Instruments 的“兴趣点”工具生成)可以说明问题:
前三个浅蓝色任务代表第一个循环。紫色任务是第二个循环。 o运行ge 任务是第三个循环。 dispatch_sync
和 dispatch_async
调用用红色 Ⓢ 标志表示。
如您所见,第一个和第三个循环表现出相同的行为,即因为您正在同步分派这些块,所以它甚至无法尝试分派下一个任务,直到先前的同步分派任务完成运行宁。但是第二个循环 运行 非常快,一个接一个地分派所有三个任务,非常快,并且这些任务 运行 相互并发,而主线程继续分派第三个循环而第二个循环派发的任务还是运行ning.
我正在尝试了解 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
我很困惑,不明白为什么会这样运行。
为什么直到第一个循环 运行 完全完成然后第二个和第三个循环才能开始 运行,对我来说,第一个循环至少应该可以被第二个循环打断,因为第二个循环可以创建新的线程来执行。
为什么第三个循环比第二个循环开始得早?
为什么第 3 个循环不能 运行 作为 运行 没有被其他任务打断的第一个循环?
你问过:
- why until 1st loop runs completely finished then 2nd and 3rd loop can start to run, to me, 1st loop should can be interrupted by 2nd loop at least, since 2nd loop can create new threads to execute.
那是因为您使用了 dispatch_sync
。这实际上是说“停止当前线程继续执行,直到分派的任务完成”。因此,在使用先前 dispatch_sync
分派的任务完成之前,第一个循环甚至不会进入其自身循环的下一次迭代。
如果您查看下图,dispatch_sync
调用是红色 Ⓢ 标志。您可以看到,在第一个分派的任务完成之前,它甚至不会分派第一个循环的第二次迭代。
- Why the 3rd loop start earlier than the 2nd loop ?
这是一个经典的比赛条件。您正在将大量任务分派到并发队列(所有全局队列都是并发队列),从技术上讲,这会按照它们排队的顺序启动它们,但是由于允许它们并发 运行,它们同时 运行 并且您无法保证 运行ces 哪个会首先到达其各自的 NSLog
语句。如果您查看与这些 NSLog
语句关联的时间戳,它们彼此非常接近(与第一个循环的 NSLog
语句不同)。
请注意,虽然从技术上讲您无法确定运行第二个或第三个循环分派的任务是否会首先开始,但有两个有趣的细节:
我们可以比较确定第三个循环的后续迭代(即迭代 7 和 8)不会在第二个循环的调度任务之前开始,因为同样,您在第三个循环中调度所有内容同步地。因此,例如,它甚至不会尝试调度迭代 7,直到迭代 6 的执行完成(而第二个循环已经异步调度其任务,并且这些任务将 运行 在该并发队列上有增无减)。
请注意,虽然您无法确定运行第二个循环分派的任务和第三个循环分派的第一个任务的时间安排,但在实践中,您通常会看到由于
dispatch_sync
的内置优化,第三个循环的第一个任务启动得更快。第二个循环使用的dispatch_async
必须做很多工作,即 GCD 必须从池中获取一个工作线程,并在该线程上启动任务。但是第三次循环的dispatch_sync
,作为一种优化,往往只是运行s当前线程上派发的任务。 (如果线程无论如何都必须等待分派的任务,为什么不直接使用它来 运行 任务并完全避免上下文切换。)这是一个技术细节,我建议您不要担心,但确实解释了为什么您经常会看到
dispatch_sync
任务比dispatch_async
启动得更快并发队列大致在同一时间。
因此,在下图中,迭代 3-5(第二个循环)和迭代 6(第三个循环的第一次迭代)的分派调用(红色 Ⓢ)发生得非常接近,以至于标志叠加在彼此之上。但是你可以在图表下方的列表中看到它们的时间。
- Why 3rd loop cannot run as the 1st loop which runs without interrupted by other tasks ?
问题不在于第一个循环 运行“没有中断”,而仅仅是队列中没有其他 运行ning,因为它是 运行ning同步地,在循环 1 完成之前,没有其他任何事情会开始。而第三个循环几乎与第二个循环的所有迭代 #3 到 5 几乎同时调度迭代 #6。
我认为查看这九个分派任务的时间线(由 Instruments 的“兴趣点”工具生成)可以说明问题:
前三个浅蓝色任务代表第一个循环。紫色任务是第二个循环。 o运行ge 任务是第三个循环。 dispatch_sync
和 dispatch_async
调用用红色 Ⓢ 标志表示。
如您所见,第一个和第三个循环表现出相同的行为,即因为您正在同步分派这些块,所以它甚至无法尝试分派下一个任务,直到先前的同步分派任务完成运行宁。但是第二个循环 运行 非常快,一个接一个地分派所有三个任务,非常快,并且这些任务 运行 相互并发,而主线程继续分派第三个循环而第二个循环派发的任务还是运行ning.