GCD 并发队列未按 FIFO 顺序启动任务
GCD concurrent queue not starting tasks in FIFO order
我有一个 class,其中包含 Jon Hoffman 掌握 Swift 中示例的两种方法。 class如下:
class DoCalculation {
func doCalc() {
var x = 100
var y = x * x
_ = y/x
}
func performCalculation(_ iterations: Int, tag: String) {
let start = CFAbsoluteTimeGetCurrent()
for _ in 0..<iterations {
self.doCalc()
}
let end = CFAbsoluteTimeGetCurrent()
print("time for \(tag): \(end - start)")
}
}
现在,在单视图模板 ViewController 的 viewDidLoad() 中,我创建了上述 class 的实例,然后创建并发队列。然后我将执行 performCalculation(: tag:) 方法的块添加到队列中。
cqueue.async {
print("Starting async1")
calculation.performCalculation(10000000, tag: "async1")
}
cqueue.async {
print("Starting async2")
calculation.performCalculation(1000, tag: "async2")
}
cqueue.async {
print("Starting async3")
calculation.performCalculation(100000, tag: "async3")
}
每次我 运行 模拟器上的应用程序,我都会随机输出开始语句。我得到的示例输出如下:
Example 1:
Starting async1
Starting async3
Starting async2
time for async2: 4.1961669921875e-05
time for async3: 0.00238299369812012
time for async1: 0.117094993591309
Example 2:
Starting async3
Starting async2
Starting async1
time for async2: 2.80141830444336e-05
time for async3: 0.00216799974441528
time for async1: 0.114436984062195
Example 3:
Starting async1
Starting async3
Starting async2
time for async2: 1.60336494445801e-05
time for async3: 0.00220298767089844
time for async1: 0.129496037960052
我不明白为什么块不以 FIFO 顺序开始。有人可以解释一下我在这里错过了什么吗?
我知道它们将同时执行,但它声明并发队列将尊重 FIFO 开始执行任务,但不能保证哪个先完成。所以至少开始任务语句应该以
开头
Starting async1
Starting async3
Starting async2
并且这个完成语句是随机的:
time for async2: 4.1961669921875e-05
time for async3: 0.00238299369812012
time for async1: 0.117094993591309
并且完成语句是随机的。
cqueue 是一个并发队列,它几乎同时将您的工作块分派给三个不同的线程(实际上取决于线程的可用性),但您无法控制每个线程完成工作的时间。
如果您想在后台队列中串行执行任务,最好使用串行队列。
let serialQueue = DispatchQueue(label: "serialQueue")
Serial Queue 只会在您的上一个任务完成后才开始队列中的下一个任务。
并发队列运行是您提交给它的作业并发这就是它的用途。
如果您想要按 FIFO 顺序排列 运行s 个作业,您需要一个串行队列。
我明白你所说的关于文档声称作业将按 FIFO 顺序提交的内容,但你的测试并没有真正确定它们 运行 的顺序。如果并发队列有 2 个线程可用,但只有一个处理器可以 运行 这些线程,它可能会在有机会打印之前换掉其中一个线程, 运行 另一个作业暂时,然后回到 运行 宁第一份工作。无法保证作业 运行 结束后才被换出。
我认为打印语句不能为您提供有关作业启动顺序的可靠信息。
对于并发队列,您可以有效地指定它们可以同时 运行。因此,当它们以 FIFO 方式添加时,您在这些不同的工作线程之间存在竞争条件,因此您无法保证哪个会首先命中其各自的 print
语句。
那么,这就提出了一个问题:你为什么要关心他们按哪个顺序打印各自的打印语句?如果顺序真的很重要,你不应该使用并发队列。或者,换句话说,如果您想使用并发队列,请编写不依赖于它们 运行.
顺序的代码
您问的是:
Would you suggest some way to get the info when a Task is dequeued from the queue so that I can log it to get the FIFO order.
如果您要问如何在实际应用程序中享受并发队列中任务的 FIFO 启动,答案是 "you don't",因为存在上述竞争条件。使用并发队列时,切勿编写严格依赖 FIFO 行为的代码。
如果您出于纯理论目的询问如何凭经验验证这一点,只需执行一些占用 CPU 并一个一个释放它们的操作即可:
// utility function to spin for certain amount of time
func spin(for seconds: TimeInterval, message: String) {
let start = CACurrentMediaTime()
while CACurrentMediaTime() - start < seconds { }
os_log("%@", message)
}
// my concurrent queue
let queue = DispatchQueue(label: label, attributes: .concurrent)
// just something to occupy up the CPUs, with varying
// lengths of time; don’t worry about these re FIFO behavior
for i in 0 ..< 20 {
queue.async {
spin(for: 2 + Double(i) / 2, message: "\(i)")
}
}
// Now, add three tasks on concurrent queue, demonstrating FIFO
queue.async {
os_log(" 1 start")
spin(for: 2, message: " 1 stop")
}
queue.async {
os_log(" 2 start")
spin(for: 2, message: " 2 stop")
}
queue.async {
os_log(" 3 start")
spin(for: 2, message: " 3 stop")
}
您将能够看到最后三个任务 运行 按 FIFO 顺序排列。
另一种方法,如果您想准确地确认 GCD 在做什么,请参考 libdispatch source code。诚然,它是非常密集的代码,所以它不是很明显,但如果你有雄心壮志,你可以深入研究它。
"I don't understand why the blocks don't start in FIFO order" 你怎么知道他们没有?他们按照先进先出的顺序开始!
问题是您无法测试。事实上,测试它的概念是不连贯的。您最快可以测试每个块的第一行 — 到 that 时间,another 行代码是完全合法的 要执行的另一个块,因为这些块是异步。这就是异步的意思。
因此,它们以 FIFO 顺序开始,但是无法保证在给定多个异步块的情况下,它们的第一行[=25]的顺序=] 将被执行。
我有一个 class,其中包含 Jon Hoffman 掌握 Swift 中示例的两种方法。 class如下:
class DoCalculation {
func doCalc() {
var x = 100
var y = x * x
_ = y/x
}
func performCalculation(_ iterations: Int, tag: String) {
let start = CFAbsoluteTimeGetCurrent()
for _ in 0..<iterations {
self.doCalc()
}
let end = CFAbsoluteTimeGetCurrent()
print("time for \(tag): \(end - start)")
}
}
现在,在单视图模板 ViewController 的 viewDidLoad() 中,我创建了上述 class 的实例,然后创建并发队列。然后我将执行 performCalculation(: tag:) 方法的块添加到队列中。
cqueue.async {
print("Starting async1")
calculation.performCalculation(10000000, tag: "async1")
}
cqueue.async {
print("Starting async2")
calculation.performCalculation(1000, tag: "async2")
}
cqueue.async {
print("Starting async3")
calculation.performCalculation(100000, tag: "async3")
}
每次我 运行 模拟器上的应用程序,我都会随机输出开始语句。我得到的示例输出如下:
Example 1:
Starting async1
Starting async3
Starting async2
time for async2: 4.1961669921875e-05
time for async3: 0.00238299369812012
time for async1: 0.117094993591309
Example 2:
Starting async3
Starting async2
Starting async1
time for async2: 2.80141830444336e-05
time for async3: 0.00216799974441528
time for async1: 0.114436984062195
Example 3:
Starting async1
Starting async3
Starting async2
time for async2: 1.60336494445801e-05
time for async3: 0.00220298767089844
time for async1: 0.129496037960052
我不明白为什么块不以 FIFO 顺序开始。有人可以解释一下我在这里错过了什么吗? 我知道它们将同时执行,但它声明并发队列将尊重 FIFO 开始执行任务,但不能保证哪个先完成。所以至少开始任务语句应该以
开头Starting async1
Starting async3
Starting async2
并且这个完成语句是随机的:
time for async2: 4.1961669921875e-05
time for async3: 0.00238299369812012
time for async1: 0.117094993591309
并且完成语句是随机的。
cqueue 是一个并发队列,它几乎同时将您的工作块分派给三个不同的线程(实际上取决于线程的可用性),但您无法控制每个线程完成工作的时间。
如果您想在后台队列中串行执行任务,最好使用串行队列。
let serialQueue = DispatchQueue(label: "serialQueue")
Serial Queue 只会在您的上一个任务完成后才开始队列中的下一个任务。
并发队列运行是您提交给它的作业并发这就是它的用途。
如果您想要按 FIFO 顺序排列 运行s 个作业,您需要一个串行队列。
我明白你所说的关于文档声称作业将按 FIFO 顺序提交的内容,但你的测试并没有真正确定它们 运行 的顺序。如果并发队列有 2 个线程可用,但只有一个处理器可以 运行 这些线程,它可能会在有机会打印之前换掉其中一个线程, 运行 另一个作业暂时,然后回到 运行 宁第一份工作。无法保证作业 运行 结束后才被换出。
我认为打印语句不能为您提供有关作业启动顺序的可靠信息。
对于并发队列,您可以有效地指定它们可以同时 运行。因此,当它们以 FIFO 方式添加时,您在这些不同的工作线程之间存在竞争条件,因此您无法保证哪个会首先命中其各自的 print
语句。
那么,这就提出了一个问题:你为什么要关心他们按哪个顺序打印各自的打印语句?如果顺序真的很重要,你不应该使用并发队列。或者,换句话说,如果您想使用并发队列,请编写不依赖于它们 运行.
顺序的代码您问的是:
Would you suggest some way to get the info when a Task is dequeued from the queue so that I can log it to get the FIFO order.
如果您要问如何在实际应用程序中享受并发队列中任务的 FIFO 启动,答案是 "you don't",因为存在上述竞争条件。使用并发队列时,切勿编写严格依赖 FIFO 行为的代码。
如果您出于纯理论目的询问如何凭经验验证这一点,只需执行一些占用 CPU 并一个一个释放它们的操作即可:
// utility function to spin for certain amount of time
func spin(for seconds: TimeInterval, message: String) {
let start = CACurrentMediaTime()
while CACurrentMediaTime() - start < seconds { }
os_log("%@", message)
}
// my concurrent queue
let queue = DispatchQueue(label: label, attributes: .concurrent)
// just something to occupy up the CPUs, with varying
// lengths of time; don’t worry about these re FIFO behavior
for i in 0 ..< 20 {
queue.async {
spin(for: 2 + Double(i) / 2, message: "\(i)")
}
}
// Now, add three tasks on concurrent queue, demonstrating FIFO
queue.async {
os_log(" 1 start")
spin(for: 2, message: " 1 stop")
}
queue.async {
os_log(" 2 start")
spin(for: 2, message: " 2 stop")
}
queue.async {
os_log(" 3 start")
spin(for: 2, message: " 3 stop")
}
您将能够看到最后三个任务 运行 按 FIFO 顺序排列。
另一种方法,如果您想准确地确认 GCD 在做什么,请参考 libdispatch source code。诚然,它是非常密集的代码,所以它不是很明显,但如果你有雄心壮志,你可以深入研究它。
"I don't understand why the blocks don't start in FIFO order" 你怎么知道他们没有?他们按照先进先出的顺序开始!
问题是您无法测试。事实上,测试它的概念是不连贯的。您最快可以测试每个块的第一行 — 到 that 时间,another 行代码是完全合法的 要执行的另一个块,因为这些块是异步。这就是异步的意思。
因此,它们以 FIFO 顺序开始,但是无法保证在给定多个异步块的情况下,它们的第一行[=25]的顺序=] 将被执行。