【问题标题】:async operation concurrency understanding异步操作并发理解
【发布时间】:2018-03-07 05:50:36
【问题描述】:

这些队列管理您提供给 GCD 的任务并以先进先出的顺序执行这些任务。这保证了添加到队列的第一个任务是队列中启动的第一个任务,添加的第二个任务将是第二个启动的任务,依此类推。 下面的代码

let anotherQueue = DispatchQueue(label: "com.gcdTest.Queue", qos: .userInteractive)
anotherQueue.async {
    anotherQueue.async{
        anotherQueue.async{
            anotherQueue.async {
                print("task 6")
                for _ in 1...300 { }
            }
        }
        print("task 3")
        for _ in 301...600 {}
    }
    anotherQueue.async{
        anotherQueue.async{
            print("task 5")
            for _ in 700...900 {}
        }
        print("task 4")
        for _ in 5000...7000 {}
    }
    print("task 1")
    for _ in 9000...10000 {}
}
anotherQueue.async {
    print("task 2")
    for _ in 1...1000 {}
}

产生输出

task 1
task 2
task 3
task 4
task 5
task 6

但是当我们在 Concurrent 中运行相同的代码时,它会产生不可预测的输出。 例如:- 将第一行代码更改为以下行

let anotherQueue = DispatchQueue(label: "com.gcdTest.Queue", qos: .userInteractive, attributes: .concurrent)

输出

task 3
task 2
task 1
task 4
task 5
task 6

根据定义,它指出 并发队列中的任务保证按照它们被添加的顺序开始......这就是你所保证的一切!

因此,期望串行队列(默认情况下)产生类似的输出。 (任务 1、任务 2、任务 3、任务 4、任务 5、任务 6) 请任何人帮助我,我哪里出错了。

【问题讨论】:

  • 既然任务是并行执行的,为什么你期望与串行任务的顺序相同?
  • 因为并发队列中的任务保证按照它们添加的顺序开始。
  • 是的,但是线程启动和print调用是分开的,线程可以启动但它可以在它结束执行print调用之前进入睡眠状态
  • 现在清楚了,谢谢 Cristik,

标签: ios swift grand-central-dispatch


【解决方案1】:

归根结底,GCD 将始终按照任务被分派到该队列的顺序启动队列中的任务。在串行队列的情况下,这意味着它们将按顺序运行,并且这种行为很容易观察到。

然而,在并发队列的情况下,虽然它会按排队顺序启动任务,但对于连续快速调度的任务,它们也可能都连续快速启动,彼此并发运行。简而言之,它们可能几乎同时开始运行,因此您无法保证会首先遇到其各自的print 语句。仅仅因为并发队列在几毫秒之后启动了一个任务,这并不能保证这两个任务遇到它们各自的print 语句的顺序。

简而言之,不是print 语句序列的确定性行为,而是与非确定性行为的简单竞争。


顺便说一句,虽然很明显您的示例在并发队列上使用时引入了竞态,但应该注意的是,由于嵌套的调度语句,您的串行队列上也会有竞态条件。看起来行为顺序在串行队列上是完全可以预测的,但事实并非如此。

让我们考虑一下您的示例的简化版本。我假设我们将从主线程开始:

queue.async {
    queue.async {
        print("task 3")
    }
    print("task 1")
}
queue.async {
    print("task 2")
}

显然,任务 1 将首先添加到队列中,如果该队列空闲,它将立即在该后台线程上启动,而主线程继续进行。但是随着主线程上的代码接近任务 2 的分派,任务 1 将启动并继续分派任务 3。您在任务 2 的分派和任务 3 之间进行了经典的竞赛。

现在,在实践中,您会看到任务 2 在任务 3 之前分派,但引入非确定性行为并不需要太多延迟。例如,在我的计算机上,如果在调度任务 2 之前,Thread.sleep(forTimeInterval: 0.00005) 表现出非确定性行为。但即使没有延迟(或一定数量的迭代的for 循环),该行为在技术上也是不确定的。


但是我们可以创建一个简单的示例来消除上述示例中隐含的竞争,但仍然说明您最初询问的串行和并发队列行为之间的区别:

for i in 0 ..< 10 {
    queue.async { [i] in
        print(i)
    }
}

这保证在串行队列上按顺序打印,但在并发队列上不一定如此。

【讨论】:

    猜你喜欢
    • 2017-09-19
    • 1970-01-01
    • 2019-10-16
    • 2020-01-17
    • 2023-03-16
    • 2011-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多