Grand Central Dispatches - 执行乱序?
Grand Central Dispatches - execution out of order?
我试图更好地理解 GCD,所以我编写了下面的测试代码(底部)。本质上是两个函数,内部等待在不同的队列上发送,主线程上的 println 等待特定任务。
我希望控制台输出应该是:
BEFORE FUNCTIONS
start 3 sec loop
start 5 sec loop
end 3 sec loop
BETWEEN FUNCTIONS WAIT ON LONG
AFTER FUNCTIONS WAIT ON LONG
end 5 sec loop
BETWEEN FUNCTIONS WAIT ON LONGER
AFTER FUNCTIONS WAIT ON LONGER
但我得到的是:
BEFORE FUNCTIONS
start 3 sec loop
start 5 sec loop
BETWEEN FUNCTIONS WAIT ON LONGER
AFTER FUNCTIONS WAIT ON LONGER
end 3 sec loop
BETWEEN FUNCTIONS WAIT ON LONG
AFTER FUNCTIONS WAIT ON LONG
end 5 sec loop
很多事情对我来说没有意义 - 1. 为什么“Longer”印在“Long”之前? 2. 为什么在较长的 END(即 5 秒函数)之后不打印“较长”的?
代码 (ViewController.swift)
import UIKit
class ViewController: UIViewController {
var longQueue = dispatch_group_create()
var longerQueue = dispatch_group_create()
var queueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
var queueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLoad() {
super.viewDidLoad()
println("BEFORE FUNCTIONS")
dispatch_async(queueHigh) {
self.longFunc()
}
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONG")
}
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONGER")
}
dispatch_async(queueLow) {
self.longerFunc()
}
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONG")
}
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONGER")
}
}
func longFunc () {
dispatch_group_enter(self.longQueue)
println("start 3 sec loop")
sleep(3)
println("end 3 sec loop")
dispatch_group_leave(self.longQueue)
}
func longerFunc() {
dispatch_group_enter(self.longerQueue)
println("start 5 sec loop")
sleep(5)
println("end 5 sec loop")
dispatch_group_leave(self.longerQueue)
}
}
这里有两类问题:
您正在进入调度组 longFunc
和 longerFunc
。但是,因为您将对这些函数的调用分派到它们各自的队列中,所以您无法保证在到达 [=14= 中执行的 dispatch_group_notify
调用之前到达 dispatch_group_enter
] 在主线程上。还请记住,分派到后台队列的代码是异步运行的,我们无法保证它何时运行与继续在主线程上执行的代码。关键是,如果它在你进入组之前遇到 dispatch_group_notify
,通知关闭将立即触发。
为了避免这种竞争情况,您要么想在将代码分派到后台队列之前(显然,在设置通知闭包之前)在 viewDidLoad
中执行 dispatch_group_enter
,或者您只想取消组的进入和离开,然后将 dispatch_async
替换为 dispatch_group_async
。这些方法中的任何一种都确保您在将通知闭包添加到这些组之前进入组。
报告“BETWEEN FUNCTIONS WAIT ON LONGER”的 dispatch notify 闭包是一个更令人震惊的例子,其中甚至没有竞争条件。您只是在分派稍后将进入组的函数之前添加通知闭包。所以这几乎肯定会在您的代码被分派到该队列之前触发。
当您使用 dispatch_async
对块进行排队时,在该块开始 运行 之前可能需要一段时间,在此期间排队线程(主线程,在您的情况下)继续 运行。然后,排队的块可能会立即开始 运行ning,在排队线程中的下一个语句 运行s.
之前
当您调用dispatch_group_notify
时,如果此时该组的条目计数为零,dispatch_group_notify
将立即将通知块排队等待执行。但是排队等待执行的块与 运行ning 块不同!如果调度队列繁忙,它不会运行立即阻塞通知。
让我们看看(我推断)事情是如何产生你的实际输出的。在每个具有重要影响的语句之后,我将描述它的作用。我还将打印操作加粗。
override func viewDidLoad() {
super.viewDidLoad()
println("BEFORE FUNCTIONS")
那行打印 BEFORE FUNCTIONS
到标准输出。
dispatch_async(queueHigh) {
self.longFunc()
}
您在 queueHigh
排队 longFunc
到 运行。它立即开始 运行ning 并在 longQueue
上调用 dispatch_group_enter
,将 longQueue
的条目数增加到 1。它还打印了 start 3 sec loop
。 然后它睡了 3 秒。它在后台线程上,因此不会阻塞主线程。
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONG")
}
当 longQueue
的条目计数为零时,您提交了一个通知块以打印 BETWEEN FUNCTIONS WAIT ON LONG
。现在不为零,因为longFunc
进入但还没有离开,所以通知块被添加到组中,而不是主队列中。
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONGER")
}
当 longerQueue
的条目计数为零时,您提交了一个通知块以打印 BETWEEN FUNCTIONS WAIT ON LONGER
。现在为零,因为 longerFunc
甚至还没有排队,所以立即将通知块添加到主队列中。但是主线程正忙于运行正在执行此方法 (viewDidLoad
),因此它还不能耗尽主队列。
dispatch_async(queueLow) {
self.longerFunc()
}
您已将 longerFunc
提交给 queueLow
。它不会立即开始 运行ning。
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONG")
}
当 longQueue
的条目计数为零时,您提交了一个通知块以打印 AFTER FUNCTIONS WAIT ON LONG
。现在不为零,因为longFunc
进了还没走,所以通知块加到群里,不是主队列。
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONGER")
}
当 longerQueue
的条目计数为零时,您提交了一个通知块以打印 AFTER FUNCTIONS WAIT ON LONGER
。现在为零,因为longerFunc
还没有开始,所以立即将通知块添加到主队列中。但是主线程正忙于运行正在执行此方法 (viewDidLoad
),因此它还不能耗尽主队列。
}
您从 viewDidLoad
返回,这让主线程继续执行主 运行 循环。以下是接下来发生的事情:
longerFunc
开始 运行ning。它在 longerQueue
上调用了 dispatch_group_enter
,将 longerQueue
的条目计数提高到 1。请注意,这不会从主队列中追溯删除这些通知块!他们在 longerQueue
的条目计数为零时排队,并且他们将保留在主队列中直到 运行,无论以后 longerQueue
的条目计数发生什么变化.
longerFunc
打印了 start 5 sec loop
然后睡了 5 秒。这是在它自己的后台线程上,所以它不会阻塞主线程或线程 运行ning longFunc
.
主线程的 运行 循环从主队列中取出一个块并执行它。这是您为 longerQueue
提交的第一个通知块。 打印了BETWEEN FUNCTIONS WAIT ON LONGER
.
主线程的 运行 循环从主队列中取出另一个块并执行它。这是您为 longerQueue
提交的第二个通知块。 打印了AFTER FUNCTIONS WAIT ON LONGER
.
主线程的 运行 循环没有发现更多的块在主队列上等待。它睡着了。
longFunc
函数完成了 3 秒的休眠。 打印了end 3 sec loop
.
longFunc
函数在 longQueue
上调用了 dispatch_group_leave
。这将 longQueue
的条目计数降低到零,因此该组将所有待处理的通知块提交到主队列。此行为唤醒了主线程的 运行 循环。
主线程的 运行 循环从主队列中取出另一个块并执行它。这是您为 longQueue
提交的第一个通知块。 打印了BETWEEN FUNCTIONS WAIT ON LONG
.
主线程的 运行 循环从主队列中取出另一个块并执行它。这是您为 longQueue
提交的第二个通知块。 打印了AFTER FUNCTIONS WAIT ON LONG
.
主线程的 运行 循环没有发现更多的块在主队列上等待。它睡着了。
longerFunc
函数完成了 5 秒的休眠。 打印了end 5 sec loop
.
longerFunc
函数在 longerQueue
上调用了 dispatch_group_leave
。这将 longerQueue
的条目计数降低为零。 longerQueue
组没有待处理的通知块,因此它什么也没做。
结束。
我试图更好地理解 GCD,所以我编写了下面的测试代码(底部)。本质上是两个函数,内部等待在不同的队列上发送,主线程上的 println 等待特定任务。
我希望控制台输出应该是:
BEFORE FUNCTIONS
start 3 sec loop
start 5 sec loop
end 3 sec loop
BETWEEN FUNCTIONS WAIT ON LONG
AFTER FUNCTIONS WAIT ON LONG
end 5 sec loop
BETWEEN FUNCTIONS WAIT ON LONGER
AFTER FUNCTIONS WAIT ON LONGER
但我得到的是:
BEFORE FUNCTIONS
start 3 sec loop
start 5 sec loop
BETWEEN FUNCTIONS WAIT ON LONGER
AFTER FUNCTIONS WAIT ON LONGER
end 3 sec loop
BETWEEN FUNCTIONS WAIT ON LONG
AFTER FUNCTIONS WAIT ON LONG
end 5 sec loop
很多事情对我来说没有意义 - 1. 为什么“Longer”印在“Long”之前? 2. 为什么在较长的 END(即 5 秒函数)之后不打印“较长”的?
代码 (ViewController.swift)
import UIKit
class ViewController: UIViewController {
var longQueue = dispatch_group_create()
var longerQueue = dispatch_group_create()
var queueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
var queueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLoad() {
super.viewDidLoad()
println("BEFORE FUNCTIONS")
dispatch_async(queueHigh) {
self.longFunc()
}
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONG")
}
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONGER")
}
dispatch_async(queueLow) {
self.longerFunc()
}
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONG")
}
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONGER")
}
}
func longFunc () {
dispatch_group_enter(self.longQueue)
println("start 3 sec loop")
sleep(3)
println("end 3 sec loop")
dispatch_group_leave(self.longQueue)
}
func longerFunc() {
dispatch_group_enter(self.longerQueue)
println("start 5 sec loop")
sleep(5)
println("end 5 sec loop")
dispatch_group_leave(self.longerQueue)
}
}
这里有两类问题:
您正在进入调度组
longFunc
和longerFunc
。但是,因为您将对这些函数的调用分派到它们各自的队列中,所以您无法保证在到达 [=14= 中执行的dispatch_group_notify
调用之前到达dispatch_group_enter
] 在主线程上。还请记住,分派到后台队列的代码是异步运行的,我们无法保证它何时运行与继续在主线程上执行的代码。关键是,如果它在你进入组之前遇到dispatch_group_notify
,通知关闭将立即触发。为了避免这种竞争情况,您要么想在将代码分派到后台队列之前(显然,在设置通知闭包之前)在
viewDidLoad
中执行dispatch_group_enter
,或者您只想取消组的进入和离开,然后将dispatch_async
替换为dispatch_group_async
。这些方法中的任何一种都确保您在将通知闭包添加到这些组之前进入组。报告“BETWEEN FUNCTIONS WAIT ON LONGER”的 dispatch notify 闭包是一个更令人震惊的例子,其中甚至没有竞争条件。您只是在分派稍后将进入组的函数之前添加通知闭包。所以这几乎肯定会在您的代码被分派到该队列之前触发。
当您使用 dispatch_async
对块进行排队时,在该块开始 运行 之前可能需要一段时间,在此期间排队线程(主线程,在您的情况下)继续 运行。然后,排队的块可能会立即开始 运行ning,在排队线程中的下一个语句 运行s.
当您调用dispatch_group_notify
时,如果此时该组的条目计数为零,dispatch_group_notify
将立即将通知块排队等待执行。但是排队等待执行的块与 运行ning 块不同!如果调度队列繁忙,它不会运行立即阻塞通知。
让我们看看(我推断)事情是如何产生你的实际输出的。在每个具有重要影响的语句之后,我将描述它的作用。我还将打印操作加粗。
override func viewDidLoad() {
super.viewDidLoad()
println("BEFORE FUNCTIONS")
那行打印 BEFORE FUNCTIONS
到标准输出。
dispatch_async(queueHigh) {
self.longFunc()
}
您在 queueHigh
排队 longFunc
到 运行。它立即开始 运行ning 并在 longQueue
上调用 dispatch_group_enter
,将 longQueue
的条目数增加到 1。它还打印了 start 3 sec loop
。 然后它睡了 3 秒。它在后台线程上,因此不会阻塞主线程。
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONG")
}
当 longQueue
的条目计数为零时,您提交了一个通知块以打印 BETWEEN FUNCTIONS WAIT ON LONG
。现在不为零,因为longFunc
进入但还没有离开,所以通知块被添加到组中,而不是主队列中。
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("BETWEEN FUNCTIONS WAIT ON LONGER")
}
当 longerQueue
的条目计数为零时,您提交了一个通知块以打印 BETWEEN FUNCTIONS WAIT ON LONGER
。现在为零,因为 longerFunc
甚至还没有排队,所以立即将通知块添加到主队列中。但是主线程正忙于运行正在执行此方法 (viewDidLoad
),因此它还不能耗尽主队列。
dispatch_async(queueLow) {
self.longerFunc()
}
您已将 longerFunc
提交给 queueLow
。它不会立即开始 运行ning。
dispatch_group_notify(longQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONG")
}
当 longQueue
的条目计数为零时,您提交了一个通知块以打印 AFTER FUNCTIONS WAIT ON LONG
。现在不为零,因为longFunc
进了还没走,所以通知块加到群里,不是主队列。
dispatch_group_notify(longerQueue, dispatch_get_main_queue()) {
println("AFTER FUNCTIONS WAIT ON LONGER")
}
当 longerQueue
的条目计数为零时,您提交了一个通知块以打印 AFTER FUNCTIONS WAIT ON LONGER
。现在为零,因为longerFunc
还没有开始,所以立即将通知块添加到主队列中。但是主线程正忙于运行正在执行此方法 (viewDidLoad
),因此它还不能耗尽主队列。
}
您从 viewDidLoad
返回,这让主线程继续执行主 运行 循环。以下是接下来发生的事情:
longerFunc
开始 运行ning。它在longerQueue
上调用了dispatch_group_enter
,将longerQueue
的条目计数提高到 1。请注意,这不会从主队列中追溯删除这些通知块!他们在longerQueue
的条目计数为零时排队,并且他们将保留在主队列中直到 运行,无论以后longerQueue
的条目计数发生什么变化.longerFunc
打印了start 5 sec loop
然后睡了 5 秒。这是在它自己的后台线程上,所以它不会阻塞主线程或线程 运行ninglongFunc
.主线程的 运行 循环从主队列中取出一个块并执行它。这是您为
longerQueue
提交的第一个通知块。 打印了BETWEEN FUNCTIONS WAIT ON LONGER
.主线程的 运行 循环从主队列中取出另一个块并执行它。这是您为
longerQueue
提交的第二个通知块。 打印了AFTER FUNCTIONS WAIT ON LONGER
.主线程的 运行 循环没有发现更多的块在主队列上等待。它睡着了。
longFunc
函数完成了 3 秒的休眠。 打印了end 3 sec loop
.longFunc
函数在longQueue
上调用了dispatch_group_leave
。这将longQueue
的条目计数降低到零,因此该组将所有待处理的通知块提交到主队列。此行为唤醒了主线程的 运行 循环。主线程的 运行 循环从主队列中取出另一个块并执行它。这是您为
longQueue
提交的第一个通知块。 打印了BETWEEN FUNCTIONS WAIT ON LONG
.主线程的 运行 循环从主队列中取出另一个块并执行它。这是您为
longQueue
提交的第二个通知块。 打印了AFTER FUNCTIONS WAIT ON LONG
.主线程的 运行 循环没有发现更多的块在主队列上等待。它睡着了。
longerFunc
函数完成了 5 秒的休眠。 打印了end 5 sec loop
.longerFunc
函数在longerQueue
上调用了dispatch_group_leave
。这将longerQueue
的条目计数降低为零。longerQueue
组没有待处理的通知块,因此它什么也没做。
结束。