如果你想演示它们同时运行,你应该分别调度这 10 个任务:
let cq = DispatchQueue(label: "downloadQueue", attributes: .concurrent)
for i in 0..<10 {
cq.async {
sleep(2)
print(i)
}
}
print("all finished queuing them!")
笔记:
你问:
所以我最初的想法是:1-10打印应该同时进行,不一定要按顺序进行。
因为它们在单个调度中,所以它们将作为单个任务运行,按顺序运行。您需要将它们放在单独的调度中以查看它们同时运行。
你继续问:
任何人都可以解释sync
调用并发队列的目的,并给我一个例子,为什么以及何时需要它?
与目标队列是串行的还是并发的sync
无关。sync
唯一规定了调用线程的行为,即调用者是否应该等待分派的任务完成。在这种情况下,你真的不想等待,所以你应该使用async
.
作为一般规则,您应该避免跟注,sync
除非 (a) 您绝对必须这样做;(b) 你愿意让调用线程阻塞直到sync
任务运行。因此,除了极少数例外,应该使用async
. 而且,也许不用说,我们从不会阻塞主线程超过几毫秒。
虽然sync
通常避免在并发调度队列上使用,但您可能会遇到的一个示例是“读写器”同步模式。在这种情况下,“读取”同步发生(因为您需要等待结果),但“写入”与屏障异步发生(因为您不需要等待,但您不希望它与任何事情同时发生其他在该队列中)。使用 GCD 进行同步的详细讨论(尤其是读写器模式)可能超出了这个问题的范围。但是在 Web 或 StackOverflow 上搜索“GCD reader-writer”,你会发现有关该主题的讨论。)
让我们以图形方式说明我修改后的代码再现,OSLog
用于在 Instruments 的“兴趣点”工具中创建间隔:
import os.log
private let log = OSLog(subsystem: "Foo", category: .pointsOfInterest)
class Foo {
func demonstration() {
let queue = DispatchQueue(label: "downloadQueue", attributes: .concurrent)
for i in 0..<10 {
queue.async { [self] in
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "async", signpostID: id, "%d", i)
spin(for: 2)
os_signpost(.end, log: log, name: "async", signpostID: id)
}
}
print("all finished queuing them!")
}
func spin(for seconds: TimeInterval) {
let start = CACurrentMediaTime()
while CACurrentMediaTime() - start < seconds { }
}
}
当我在 Instruments(例如“Product”»“Profile”)中对此进行分析时,选择“Time Profiler”模板(其中包括“Points of Interest”工具),我会看到正在发生的事情的图形时间线:
因此,让我提请您注意上述两个有趣的方面:
并发队列并发运行任务,但是因为我的 iPhone 的 CPU 只有六个内核,所以实际上只有六个内核可以同时运行。接下来的四个将不得不等到该特定工作线程有可用的核心。
请注意,此演示之所以有效,是因为我不仅在调用sleep
,而是在旋转所需的时间间隔,以更准确地模拟一些缓慢的阻塞任务。对于慢速同步任务,旋转是比它更好的代理sleep
。
正如您所指出的,这说明并发任务可能不会按照它们提交的确切顺序出现。这是因为(a)他们都排得太快了;(b) 它们同时运行:对于哪个并发运行的线程首先到达日志记录语句(或“兴趣点”间隔)存在“竞争”。
归根结底,对于那些同时运行的任务,由于比赛,它们可能看起来没有按顺序运行。这就是并发执行的工作原理。