这些队列管理您向GCD提供的任务,并按FIFO顺序执行这些任务。这保证了添加到队列中的第一个任务是队列中启动的第一个任务,添加的第二个任务将是第二个启动的任务,依此类推。 下面的代码
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
根据定义,它说明 并发队列中的任务保证按照添加的顺序开始......这就是所有你保证的!
因此,期望串行队列产生类似的输出(默认情况下)。 (task1,task2,task3,task4,task5,task6) 请任何人帮助我,我出错了。
答案 0 :(得分: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之间存在经典竞争。
现在,在实践中,您将看到在任务3之前调度任务2,但引入非确定性行为并不需要太多延迟。例如,在我的计算机上,如果在调度任务2之前,Thread.sleep(forTimeInterval: 0.00005)
表现出非确定性行为。但即使没有延迟(或一定次数的迭代的for
循环),该行为在技术上也是不确定的。
但是我们可以创建简单的示例来消除上面示例中隐含的种族,但仍然说明了您最初询问的串行和并发队列行为之间的区别:
for i in 0 ..< 10 {
queue.async { [i] in
print(i)
}
}
保证在串行队列上按顺序打印,但不一定在并发队列上打印。