1-1.【GCD】DispatchQueue 本质上是什么?它和线程是什么关系?

76 阅读3分钟

在 iOS 并发编程中,DispatchQueue(GCD) 是一个基于 任务队列 的抽象层。它的出现是为了让开发者从繁琐的“线程生命周期管理”中解脱出来。


1. 核心本质:它是一个数据结构

DispatchQueue 本质上是一个 先进先出(FIFO)的闭包队列

当你向队列中提交一个任务(Block/Closure)时,你只是把一段代码逻辑放进了一个待处理的链表中。它并不直接执行代码,而是负责 调度(Scheduling)

其底层依赖于:

  • 线程池(Thread Pool) :由系统内核维护的一组工作线程。
  • pthread:底层的 POSIX 线程库。
  • Mach 任务:系统内核级的调度单位。

2. 队列与线程的关系:调度员与执行者的关系

如果把执行代码比作“修路”,那么:

  • 线程 (Thread) :就是实际干活的“工人”。
  • DispatchQueue:就是“工单管理器”。

关键映射逻辑

线程与队列之间并没有固定的一一对应关系(除了主队列):

  1. 动态绑定:当你从队列中取出任务时,GCD 会从系统全局线程池中捞出一个空闲的线程,把任务塞给它去执行。执行完后,线程不会销毁,而是回到池子里等待下一个工单。

  2. 串行 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 { ... } 时:

  1. 任务被封装成 dispatch_continuation_t 结构体。
  2. 放入队列的链表后端。
  3. GCD 通知内核(通过 kevent 等系统调用)有新任务。
  4. 内核检查 根队列(Root Queue)
  5. 如果有空闲工作线程,唤醒它;如果没有,根据需要创建。
  6. 工作线程执行任务,并在结束后询问队列:“还有活吗?”

💡 深度启发:线程爆炸 (Thread Explosion)

尽管 GCD 很好用,但如果你向 Concurrent Queue 异步提交了大量会导致阻塞(比如同步网络请求或文件 IO)的任务,GCD 会误以为线程不够用而不断创建新线程。这会导致线程爆炸,使系统陷入频繁切换上下文的泥潭。