自定义队列本身不拥有线程;它们通过 target queue 机制,最终把任务“汇流”到某个全局并发队列(按 QoS)上执行。
下面我们从 结构 → 映射路径 → 调度细节 → 常见误解 一层层拆。
1️⃣ 核心模型:Queue ≠ Thread(再强调一次)
在 GCD 里:
- 队列(DispatchQueue) :
👉 任务的组织和调度规则 - 线程(worker thread) :
👉 真正执行任务的资源
👉 自定义队列永远不直接绑定线程
2️⃣ 自定义队列创建时发生了什么?
let queue = DispatchQueue(
label: "com.example.myqueue",
qos: .utility,
attributes: [.concurrent]
)
你得到的是一个:
- 有自己 FIFO / 并发规则的 逻辑队列
- 没有线程
- 没有独立线程池
3️⃣ 真正关键的机制:target queue
每一个 dispatch queue 都有一个 target queue
你可以显式指定:
queue.setTarget(queue: DispatchQueue.global(qos: .utility))
但即使你不写:
系统也会自动帮你设
默认 target 规则是:
| 队列类型 | 默认 target |
|---|---|
| main queue | main thread / RunLoop |
| global queue | root queue(系统内部) |
| 自定义 queue | 对应 QoS 的 global queue |
4️⃣ 调度路径(重点流程)
当你执行:
queue.async {
work()
}
真实发生的是:
你的自定义 queue
↓(保持自己的串行 / 并发语义)
target queue(global queue, by QoS)
↓
GCD 调度器
↓
共享线程池(worker threads)
↓
执行 block
👉 自定义队列只负责:
- 任务顺序
- barrier
- 串行 / 并发约束
👉 全局队列负责:
- 线程选择
- QoS 调度
- CPU 分配
5️⃣ 串行队列是如何“跑在并发队列上”的?
这点很容易让人迷糊。
let serial = DispatchQueue(label: "serial")
虽然:
- serial queue 自己是 串行的
- 但它的 target 是 并发的 global queue
为什么还能串行?
原因是:
串行语义是在“队列层”保证的,不是在“线程层”
GCD 保证:
- 同一时间
- serial queue 只会向 target queue 提交 一个 block
即使 target 是并发的:
- 永远只会有一个来自该 serial queue 的任务在执行
6️⃣ 并发自定义队列又是如何工作的?
let concurrent = DispatchQueue(
label: "concurrent",
attributes: .concurrent
)
特点:
- 并发规则由 这个队列自己管理
- 多个 block 可以同时被提交给 target queue
- target queue(global)负责并行执行
7️⃣ barrier 为什么只能用于自定义并发队列?
queue.async(flags: .barrier) {
criticalSection()
}
原因很直白:
-
barrier 是 队列内部的语义
-
global queue 是:
- 共享的
- 多来源的
-
GCD 无法也不允许:
- 为“全局公共队列”插入独占屏障
👉 所以:
barrier 只能作用在你“拥有”的那一层队列
8️⃣ QoS 是如何一路传下去的?
-
自定义队列有自己的 QoS
-
提交的 block 继承:
- queue QoS
- 或 caller QoS(取更高)
-
最终:
- 映射到 target global queue
- 再映射到线程优先级
👉 QoS 是任务属性,不是队列绑定线程。
9️⃣ 一个容易忽略但很重要的点
setTarget(queue:) 可以改变的不只是 QoS
你可以:
queue.setTarget(queue: someSerialQueue)
结果是:
-
多个队列“汇流”到一个串行队列
-
从而实现:
- 资源限流
- 顺序控制
- 自定义调度树
这就是 GCD 的“队列树(queue hierarchy)”设计。
🔚 一句话终极总结
自定义队列通过 target queue 机制映射到对应 QoS 的全局并发队列执行;它们只定义任务的组织与同步语义,而线程管理与调度完全由全局队列和系统线程池负责。