DispatchQueue 的底层实现是一个高度复杂的 C 语言结构体,定义在 Apple 的开源库 libdispatch(也称为 GCD)中。它并不是一个单一的数组或链表,而是一个分层的、高度优化的对象。
其最核心的底层数据结构是 dispatch_queue_t,在源码层面,它实际上是指向 struct dispatch_queue_s 的指针。
1. 核心结构体组成
一个 dispatch_queue_s 结构体包含了管理任务和状态的关键字段:
-
dq_items_head/dq_items_tail:这是队列的本质——一个 基于链表的 FIFO(先进先出)任务队列。当你调用
async时,任务会被封装成dispatch_continuation_t节点并挂载到这个链表上。 -
dq_state:这是一个 64 位的原子变量,存储了队列的当前状态,包括:
- 是否已挂起(Suspended)。
- 是否有活跃的任务(Width/Concurrent count)。
- 是否正在被某个线程处理(Dirty bits)。
-
dq_targetq(目标队列) :这是 GCD 最精妙的设计。每个用户创建的队列都有一个指向“目标队列”的指针,最终指向系统定义的 Root Queues(全局并发队列) 。这种层级结构决定了任务最终在哪组线程池中执行。
2. 任务单元:dispatch_continuation_s
你提交的每个闭包(Closure)在底层都会被包装成一个 Continuation 结构体。它包含:
- 函数指针(指向你的 Swift/OC 代码块)。
- 上下文数据(Context,即闭包捕获的变量)。
- 优先级信息(QoS)。
3. 并发控制:位图与原子操作
为了保证性能,GCD 在管理队列状态时极少使用传统的 mutex 锁,而是大量使用 原子操作(Atomic Operations) 。
- 串行队列:其
dq_width为 1。底层通过dq_state的原子竞争来确保同一时间只有一个线程能“认领”该队列的任务。 - 并发队列:其
dq_width很大。它允许从链表中同时取出多个任务并分发给不同的线程。
4. 与内核的纽带:vnode 与 kevent
在更底层的 libdispatch 内部,它使用 dispatch_object_s 作为基类,构建了一套模拟面向对象的层次结构。
当队列中有新任务时,GCD 不会不停地轮询。它利用 kevent(内核事件通知机制) 与操作系统内核通信。内核负责监控队列状态,并在有任务且有 CPU 空闲时,唤醒工作线程去消费任务链表。
5. 总结:队列的“骨架”
| 组件 | 底层数据结构 | 作用 |
|---|---|---|
| 队列对象 | dispatch_queue_s | 存储状态、宽度和链表指针。 |
| 任务节点 | dispatch_continuation_s | 封装用户闭包和执行环境。 |
| 状态控制 | uint64_t (Atomic State) | 通过位运算实现无锁的状态切换。 |
| 分发层级 | dq_targetq 指针 | 将用户任务向上合并到系统线程池。 |
💡 深度启发:为什么自定义队列比全局队列慢一点点?
因为自定义队列必须经过一次或多次 dq_targetq 的查找(由用户队列 -> 根队列),而全局队列直接对接工作池。虽然这个开销在现代硬件上可以忽略不计,但这体现了 GCD 层层递进的层级设计。