1-8.【GCD】并行队列一定并发执行吗?在什么情况下不会?

2 阅读2分钟

不一定。并行队列(concurrent queue)只“允许并发”,但并不“保证并发”。
这是 GCD 里一个非常容易被“名字误导”的点。

先给一句可以直接背的结论👇

并行队列的语义是“任务之间没有顺序依赖”,是否真的并发执行,取决于线程、系统资源和调度条件。

下面按「不会并发」的典型场景逐一拆。


1️⃣ 最根本原因:没有足够的线程

只有一个可用线程时

  • 单核设备
  • 系统负载极高
  • 线程池被限制(省电 / 后台)
DispatchQueue.global().async { workA() }
DispatchQueue.global().async { workB() }

逻辑上可并发,但实际可能:

A 执行完
↓
B 执行

👉 并行 ≠ 并发 ≠ 并行执行(parallelism)


2️⃣ 被“人为串行化”的情况(非常常见)

① 并行队列 + barrier

queue.async(flags: .barrier) {
    critical()
}
  • barrier 执行时:

    • 队列表现为 临时串行
    • 只有它一个在跑
  • 前后的任务都被挡住


② target 到串行队列

let concurrent = DispatchQueue(label: "c", attributes: .concurrent)
concurrent.setTarget(queue: someSerialQueue)

结果:

  • concurrent queue 的所有任务
  • 最终在 someSerialQueue 上排队
  • 完全不并发

👉 “并行能力”被 target queue 吃掉了。


3️⃣ 同步调用(sync)天然会限制并发

sync 本身是阻塞语义

queue.sync {
    work()
}
  • 调用线程被阻塞

  • 如果调用发生在:

    • 当前队列
    • 或受限线程环境
  • 实际表现为:

    • 顺序执行

4️⃣ QoS / 优先级导致“看起来不并发”

DispatchQueue.global(qos: .background).async { workA() }
DispatchQueue.global(qos: .background).async { workB() }

在以下情况下:

  • App 后台
  • 低电量模式
  • 屏幕锁定

系统可能:

  • 只给 1 条 worker thread
  • 甚至暂停调度

👉 不是队列不并行,是 系统不让你并发


5️⃣ 任务本身阻塞 / 占用资源

典型反例:I/O、锁、sleep

queue.async {
    lock.lock()
    // long work
    lock.unlock()
}
  • 多个任务虽然同时开始
  • 但都卡在同一把锁
  • 表现为串行

或者:

queue.async {
    sleep(5)
}
  • worker thread 被占住
  • 后续任务只能等

6️⃣ Swift Concurrency 的“假并发”情况

await withTaskGroup(of: Void.self) { group in
    for _ in 0..<10 {
        group.addTask {
            await someActor.doWork()
        }
    }
}
  • 所有任务并发创建

  • 但:

    • 都进了同一个 actor
  • actor 是串行的

👉 结构并发 ≠ 实际并发


7️⃣ 正确的心智模型(很重要)

概念含义
并行队列允许多个任务同时被调度
并发执行任务在时间上重叠
并行执行多核同时跑

并行队列 ≠ 并发保证器


8️⃣ 一句话终极总结(请背)

并行队列只表示任务之间没有顺序约束;在缺乏线程资源、被 barrier / target 串行化、受 QoS 或系统策略限制、或任务自身阻塞的情况下,并行队列完全可能表现为串行执行。