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]的顺序=] 将被执行。