✅ 结论版(先背这个)
系统一共有 6 组全局并发队列(global concurrent queues),按 QoS(服务质量)区分;
每一组并不是“一个队列”,而是一类共享同一 QoS 的并发队列入口,最终映射到同一个线程池。
1️⃣ 到底有多少个全局并发队列?
从 GCD 语义层面看:
🧩 一共 6 种 QoS,对应 6 组全局并发队列
| QoS | Swift 枚举 | 用途 |
|---|---|---|
| User Interactive | .userInteractive | UI 渲染、动画、立即响应 |
| User Initiated | .userInitiated | 用户触发、马上要结果 |
| Default | .default | 默认优先级 |
| Utility | .utility | I/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 权重、优先级继承和系统策略实现强隔离,从而既节省线程资源,又保证高优先级任务的响应性。