GCD并发队列不按FIFO顺序启动任务

时间:2017-08-19 12:51:39

标签: ios swift3 grand-central-dispatch

我有一个类,其中包含两个方法,根据Jon Hoffman的Mastering Swift中的示例。课程如下:

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()中,我创建了上面一个类的实例,然后创建了一个并发队列。然后我将执行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")
}

每次我在模拟器上运行应用程序时,都会随机输出start语句。我得到的示例输出如下:

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

和随机完成报表。

4 个答案:

答案 0 :(得分:3)

并发队列运行您提交给它的作业并发这就是它的用途。

如果您希望队列按FIFO顺序运行作业,则需要一个串行队列。

我看到你所说的文件声称这些工作将以FIFO顺序提交,但你的测试并没有真正确定它们的运行顺序。如果并发队列有2个线程可用,但只有一个处理器可以运行这些线程,它可能会在有机会打印之前换掉其中一个线程,运行另一个作业一段时间,然后再返回运行第一个线程工作。在换出之前,无法确保作业运行到最后。

我认为print语句不会为您提供有关作业启动顺序的可靠信息。

答案 1 :(得分:0)

cqueue是一个并发队列,它几乎同时将你的工作块调度到三个不同的线程(它实际上取决于线程的可用性),但你无法控制每个线程的时间线程完成工作。

如果要在后台队列中以串行方式执行任务,则使用串行队列会更好。

let serialQueue = DispatchQueue(label: "serialQueue")

Serial Queue仅在您的上一个任务完成时才会启动队列中的下一个任务。

答案 2 :(得分:0)

使用并发队列,您可以有效地指定它们可以同时运行。因此,虽然它们以FIFO方式添加,但是在这些不同的工作线程之间存在竞争条件,因此您无法保证哪个将首先达到其各自的print语句。

所以,这提出了一个问题:为什么你关心他们打印各自的打印声明的顺序?如果订单非常重要,则不应使用并发队列。或者,另一种说法是,如果要使用并发队列,请编写不依赖于它们运行顺序的代码。

你问:

  

当一个Task从队列中出队时,你会建议一些方法来获取信息,以便我可以记录它以获得FIFO顺序。

如果您正在询问如何在真实应用程序中享受并发队列上任务的FIFO启动,则答案是“您没有”,因为上述竞争条件。使用并发队列时,切勿编写严格依赖于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。它无疑是相当密集的代码,所以它并不是很明显,但如果你有野心的话,你可以深入研究它。

答案 3 :(得分:0)

“我不明白为什么这些块不按FIFO顺序启动”你怎么知道它们没有?它们按FIFO顺序启动

问题是你无法测试。事实上,测试它的概念是不连贯的。最快的是你可以测试任何东西是每个块的第一行 - 并且通过那个时间,另一个代码来自另一个是完全合法的>阻止执行,因为这些块是异步。这就是异步意味着

所以,他们以FIFO顺序启动,但不能保证在给定多个异步块的情况下,它们的第一行将被执行。