在 iOS 并发编程中,DispatchQueue(GCD) 是一个基于 任务队列 的抽象层。它的出现是为了让开发者从繁琐的“线程生命周期管理”中解脱出来。
1. 核心本质:它是一个数据结构
DispatchQueue 本质上是一个 先进先出(FIFO)的闭包队列。
当你向队列中提交一个任务(Block/Closure)时,你只是把一段代码逻辑放进了一个待处理的链表中。它并不直接执行代码,而是负责 调度(Scheduling) 。
其底层依赖于:
- 线程池(Thread Pool) :由系统内核维护的一组工作线程。
- pthread:底层的 POSIX 线程库。
- Mach 任务:系统内核级的调度单位。
2. 队列与线程的关系:调度员与执行者的关系
如果把执行代码比作“修路”,那么:
- 线程 (Thread) :就是实际干活的“工人”。
- DispatchQueue:就是“工单管理器”。
关键映射逻辑
线程与队列之间并没有固定的一一对应关系(除了主队列):
-
动态绑定:当你从队列中取出任务时,GCD 会从系统全局线程池中捞出一个空闲的线程,把任务塞给它去执行。执行完后,线程不会销毁,而是回到池子里等待下一个工单。
-
串行 vs 并行:
- 串行队列 (Serial) :同一时间只向系统申请 1 个 线程来按顺序干活。
- 并行队列 (Concurrent) :根据任务数量和系统负载,同时申请 多个 线程来干活。
3. 为什么不直接操作线程?
通过 DispatchQueue 替代 Thread 的核心优势在于 管理成本:
| 维度 | 直接使用 Thread | 使用 DispatchQueue (GCD) |
|---|---|---|
| 创建开销 | 昂贵。每个线程约占 512KB - 1MB 栈内存。 | 极轻量。只是队列中的一个对象。 |
| 切换成本 | 高。频繁的上下文切换(Context Switch)损耗 CPU。 | 低。GCD 会根据 CPU 核心数自动调节活跃线程数。 |
| 管理复杂度 | 需要手动处理生命周期、同步和锁。 | 自动管理。只需关注队列逻辑(串行或并行)。 |
| 线程爆炸 | 容易创建过多线程导致 OOM 或系统卡顿。 | 内核级线程池防止资源耗尽。 |
4. 特殊关系:主队列与主线程
虽然大多数队列与线程是解耦的,但 Main Queue 是个例外:
- 主队列是一个特殊的串行队列,它关联且仅关联 Main Thread。
- 所有提交给主队列的任务,最终一定会在主线程的
RunLoop中被处理。这也是为什么 UI 更新必须回到主队列的原因。
5. 底层视角:它是如何运行的?
当你调用 queue.async { ... } 时:
- 任务被封装成
dispatch_continuation_t结构体。 - 放入队列的链表后端。
- GCD 通知内核(通过
kevent等系统调用)有新任务。 - 内核检查 根队列(Root Queue) 。
- 如果有空闲工作线程,唤醒它;如果没有,根据需要创建。
- 工作线程执行任务,并在结束后询问队列:“还有活吗?”
💡 深度启发:线程爆炸 (Thread Explosion)
尽管 GCD 很好用,但如果你向 Concurrent Queue 异步提交了大量会导致阻塞(比如同步网络请求或文件 IO)的任务,GCD 会误以为线程不够用而不断创建新线程。这会导致线程爆炸,使系统陷入频繁切换上下文的泥潭。