1-5.【GCD】全局并发队列(global queue)有多少个?是如何区分的?

9 阅读5分钟

✅ 结论版(先背这个)

系统一共有 6 组全局并发队列(global concurrent queues),按 QoS(服务质量)区分;
每一组并不是“一个队列”,而是一类共享同一 QoS 的并发队列入口,最终映射到同一个线程池。


1️⃣ 到底有多少个全局并发队列?

GCD 语义层面看:

🧩 一共 6 种 QoS,对应 6 组全局并发队列

QoSSwift 枚举用途
User Interactive.userInteractiveUI 渲染、动画、立即响应
User Initiated.userInitiated用户触发、马上要结果
Default.default默认优先级
Utility.utilityI/O、网络、数据处理
Background.background用户不可见的后台任务
Unspecified.unspecified系统内部使用

调用方式:

DispatchQueue.global(qos: .userInitiated)

⚠️ 注意:
你拿到的不是“一个独立队列实例”,而是一个“QoS 对应的入口”


2️⃣ 那为什么看起来像“无限多个”?

因为这段代码:

let q1 = DispatchQueue.global(qos: .utility)
let q2 = DispatchQueue.global(qos: .utility)
  • q1 === q2 ❌(对象不一定相等)
  • 但它们调度到的是同一套执行资源

👉 GCD 的设计是:

逻辑上提供多个 queue handle,
物理上共享同一个 QoS 对应的线程池


3️⃣ 全局并发队列是如何“区分”的?

✅ 核心区分维度:QoS(不是线程数)

QoS 决定什么?

  • 线程调度优先级
  • CPU 时间分配
  • 是否更容易抢占别的任务
  • 能否影响 UI 流畅度
  • 能否被系统延迟 / 挂起

例如:

DispatchQueue.global(qos: .background).async {
    // 系统允许你慢慢来
}

DispatchQueue.global(qos: .userInteractive).async {
    // 系统会尽可能立刻执行
}

4️⃣ 那每个 QoS 有多少线程?

👉 没有固定数量

GCD 使用的是:

动态线程池(work-stealing thread pool)

线程数量取决于:

  • CPU 核心数
  • 当前系统负载
  • 任务是否阻塞(I/O)
  • 能耗策略(尤其在 iOS)

例如:

  • 8 核设备
  • .userInitiated 可能同时跑 6–8 个线程
  • .background 可能只给 1–2 个,甚至被暂停

5️⃣ 一个很容易误解的点(重点⚠️)

❌ 错误理解

“每个 QoS 是一个独立的并发队列”

✅ 正确理解

每个 QoS 是一类调度优先级 + 共享线程池,
DispatchQueue.global 只是一个提交入口

你不能:

  • 暂停 global queue
  • barrier global queue
  • 控制它的最大并发数

这些只有 自建并发队列 才能做到。


6️⃣ global queue vs 自建并发队列

特性global queue自建 concurrent queue
数量固定 6 组任意
QoS系统定义可自定义
线程池系统共享仍然用系统池
barrier❌ 不支持✅ 支持
suspend
控制并发部分可控

7️⃣ 一句话总结(面试杀器)

GCD 中并不存在“很多个全局并发队列实例”,而是按 QoS 划分的 6 组全局并发执行环境;DispatchQueue.global(qos:) 只是获取对应 QoS 的调度入口,底层由系统统一的线程池动态执行。

👉 追问:不同 QoS 的 global queue 是否共享线程池?

是的,不同 QoS 的 global queue 在实现层面共享同一个底层线程池,但在调度层面被 QoS 强烈隔离和加权;它们共享“人手”,但不共享“优先权”。

下面把这个听起来有点绕的结论拆开。


1️⃣ 先把“共享”说清楚:共享的是什么?

✅ 共享的是:线程池(worker threads)

  • GCD 并不是:

    • 每个 QoS 一组固定线程
  • 而是:

    • 一个(或少量)全局 worker thread pool
    • 线程是系统资源,可复用

当某个线程空闲时:

  • 可以去执行:

    • .background 的任务
    • 也可以去执行 .userInitiated 的任务
  • 线程本身不“属于”某个 QoS

👉 所以从物理层面讲:是共享的


2️⃣ 那 QoS 的作用在哪?(关键)

QoS 决定的是:调度优先级,不是线程归属

调度时,GCD 会考虑:

  • 任务的 QoS
  • 线程当前的优先级
  • 系统负载
  • 能耗 / 前后台状态

然后做两件事:

🔹 1. 决定“先跑谁”

  • 高 QoS 任务:

    • 更容易被调度
    • 更少被延迟
  • 低 QoS 任务:

    • 可能被推迟
    • 甚至暂时不调度

🔹 2. 提升线程优先级(QoS override)

当一个 worker thread 去执行高 QoS 任务时:

  • 线程的调度优先级会被临时提升
  • 执行完再降回来

👉 线程是“变色龙”,任务决定颜色。


3️⃣ 一个非常重要的机制:QoS 继承 / 提升

来看一个经典场景:

DispatchQueue.global(qos: .background).async {
    DispatchQueue.global(qos: .userInteractive).sync {
        // 高优先级任务
    }
}

如果不做特殊处理:

  • background 线程
  • 执行 userInteractive
  • UI 会卡 💀

所以 GCD 有:

QoS override(优先级反转保护)

  • 当低 QoS 线程执行高 QoS block 时
  • 线程会被临时提升到高 QoS
  • 防止高优先级任务被“拖慢”

这再次说明:
👉 线程池是共享的,但优先级是动态的


4️⃣ 那是不是“完全共享、毫无隔离”?不是。

❌ 不是一个“任务大杂烩池”

系统会做软隔离

  • 高 QoS:

    • 有最低执行保障
    • 更积极唤醒线程
  • 低 QoS:

    • 在资源紧张时被限流
    • 可能暂停

例如:

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

.background 任务可能直接不跑。


5️⃣ 和 Swift Concurrency 的关系(顺一下)

Swift Concurrency 里的:

Task {
    // 默认继承当前 QoS
}

Task.detached(priority: .background) {
    ...
}

最终:

  • 仍然落到 GCD 的全局线程池
  • TaskPriority → 映射到 GCD QoS

所以:

Swift Concurrency 并没有另起炉灶,它复用的就是这套 QoS + 共享线程池模型


6️⃣ 常见误解对照表

误解真相
每个 QoS 有自己的一组线程❌ 共享线程
background 任务永远慢❌ 只是优先级低
高 QoS 一定独占 CPU❌ 只是更容易被调度
QoS = 线程优先级❌ QoS 是任务属性

7️⃣ 一句话终极总结

不同 QoS 的 global queue 在物理层面共享同一个 GCD 线程池,但在调度层面通过 QoS 权重、优先级继承和系统策略实现强隔离,从而既节省线程资源,又保证高优先级任务的响应性。