为什么带有同步的并发队列就像串行队列一样?

flo*_*liu 4 concurrency grand-central-dispatch concurrent-queue swift

谁能帮助我理解我创建的这段代码:

let cq = DispatchQueue(label: "downloadQueue", attributes: .concurrent)
cq.sync {
for i in 0..<10 {
    sleep(2)
    print(i)
  }
}

print("all finished!")
Run Code Online (Sandbox Code Playgroud)

输出是串行顺序 1->10,中间等待 2 秒。最后会打印出all finished

我明白最后一部分。然而我的问题是:

并发队列不应该同时启动多个任务吗?所以我最初的想法是:应该按1-10打印concurrently,不一定按序列顺序。

谁能解释一下sync并发队列调用的目的,并给我一个例子,为什么以及何时需要它?

Rob*_*Rob 8

如果您想演示它们同时运行,您应该单独调度 10 个任务:

\n
let cq = DispatchQueue(label: "downloadQueue", attributes: .concurrent)\n\nfor i in 0..<10 {\n    cq.async {\n        sleep(2)\n        print(i)\n    }\n}\nprint("all finished queuing them!")\n
Run Code Online (Sandbox Code Playgroud)\n

笔记:

\n
    \n
  • 有 10 个调度到并发队列,而不是一个。

    \n

    每个调度的任务相对于调度到该队列的其他任务并发运行(这就是为什么我们需要多个调度来说明并发性)。

    \n
  • \n
  • 另请注意,我们异步调度,因为我们不想在调度下一个任务之前调用队列来等待每个已调度的任务。

    \n
  • \n
\n
\n

你问:

\n
\n

所以我最初的想法是:1-10打印应该同时进行,不一定按串行顺序进行。

\n
\n

因为它们位于单个调度内,所以它们将作为单个任务运行,按顺序运行。您需要将它们放在单独的调度中才能看到它们同时运行。

\n

你继续问:

\n
\n

谁能解释一下sync并发队列调用的目的,并给我一个例子,为什么以及何时需要它?

\n
\n

sync与目标队列是串行还是并发无关。唯一指示调用sync线程的行为,即调用者是否应该等待分派任务完成。在这种情况下,您确实不想等待,因此您应该使用.async

\n

作为一般规则,您应该避免致电,sync除非 (a) 您绝对必须这样做;(b) 您愿意阻塞调用线程直到sync任务运行。因此,除了极少数例外,人们应该使用async. 而且,也许不用说,我们永远不会阻塞主线程超过几毫秒。

\n

虽然sync通常会避免在并发调度队列上使用,但您可能会遇到的一个示例是 \xe2\x80\x9creader-writer\xe2\x80\x9d 同步模式。在这种情况下, \xe2\x80\x9creads\xe2\x80\x9d 同步发生(因为你需要等待结果),但 \xe2\x80\x9cwrites\xe2\x80\x9d 与屏障异步发生(因为你不需要等待,但您不希望它与该队列上的其他任何内容同时发生)。使用 GCD 进行同步(尤其是读写器模式)的详细讨论可能超出了这个问题的范围。但是在网络或 StackOverflow 中搜索 \xe2\x80\x9cGCD reader-writer\xe2\x80\x9d,你会找到有关该主题的讨论。)

\n
\n

让我们以图形方式说明我对代码的改进再现,使用OSLog在 Instruments\xe2\x80\x99 \xe2\x80\x9cPoints of Interest\xe2\x80\x9d 工具中创建间隔:

\n
import os.log\n\nprivate let log = OSLog(subsystem: "Foo", category: .pointsOfInterest)\n\nclass Foo {\n    func demonstration() {\n        let queue = DispatchQueue(label: "downloadQueue", attributes: .concurrent)\n\n        for i in 0..<10 {\n            queue.async { [self] in\n                let id = OSSignpostID(log: log)\n                os_signpost(.begin, log: log, name: "async", signpostID: id, "%d", i)\n                spin(for: 2)\n                os_signpost(.end, log: log, name: "async", signpostID: id)\n            }\n        }\n        print("all finished queuing them!")\n    }\n\n    func spin(for seconds: TimeInterval) {\n        let start = CACurrentMediaTime()\n        while CACurrentMediaTime() - start < seconds { }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当我在仪器中对此进行分析时(例如 \xe2\x80\x9cProduct\xe2\x80\x9d \xc2\xbb \xe2\x80\x9cProfile\xe2\x80\x9d),选择 \xe2\x80\x9cTime Profiler\xe2\x80 \x9d 模板(其中包括 \xe2\x80\x9cPoints of Interest\xe2\x80\x9d 工具),我看到了正在发生的事情的图形时间线:

\n

在此输入图像描述

\n

因此,让我提请您注意上述两个有趣的方面:

\n
    \n
  1. 并发队列并发运行任务,但由于我的iPhone\xe2\x80\x99s CPU只有六个核心,所以实际上只有六个核心可以同时运行。接下来的四个线程必须等待,直到有一个核心可用于该特定的工作线程。

    \n

    请注意,此演示之所以有效,是因为我不仅在调用sleep,而且还在旋转所需的时间间隔,以更准确地模拟一些缓慢、阻塞的任务。旋转是比慢速同步任务更好的代理sleep。

    \n
  2. \n
  3. 正如您所指出的,这说明并发任务可能不会按照提交的精确顺序显示。这是因为(a)他们都很快就连续排队了;(b) 它们同时运行:有一个 \xe2\x80\x9crace\xe2\x80\x9d 来确定哪个并发运行的线程获取日志语句(或 \xe2\x80\x9c 兴趣点\xe2\x80\x9d间隔)首先。

    \n

    最重要的是,对于那些同时运行的任务,由于竞争,它们可能看起来不按顺序运行。这就是并发执行的工作原理。

    \n
  4. \n
\n