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

53 阅读6分钟

DispatchQueue 是 GCD(Grand Central Dispatch)的核心抽象,它本质上是一个 任务调度管理器,而不是线程本身。

本质特性

1. 任务队列

  • 存储要执行的任务(闭包或函数)
  • 遵循 FIFO(先进先出)原则
  • 可以是串行(serial)或并发(concurrent)
// 串行队列 - 一次执行一个任务
let serialQueue = DispatchQueue(label: "com.example.serial")

// 并发队列 - 可同时执行多个任务
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

2. 线程池管理器

  • 管理底层线程池(thread pool)
  • 自动复用线程,避免频繁创建销毁
  • 根据系统负载动态调整线程数

与线程的关系

关键区别

DispatchQueue线程 (Thread)
高级抽象,任务调度器低级执行单元
管理任务执行顺序CPU调度的基本单位
自动线程管理需要手动管理生命周期
系统优化负载均衡固定资源占用

实际关系

// 示例:队列与线程的关系
let queue = DispatchQueue(label: "test", attributes: .concurrent)

for i in 1...5 {
    queue.async {
        print("任务 (i) 在线程: (Thread.current)")
    }
}

// 可能输出(线程会被复用):
// 任务1在线程: <NSThread: 0x...>{number = 4, name = (null)}
// 任务2在线程: <NSThread: 0x...>{number = 5, name = (null)}
// 任务3在线程: <NSThread: 0x...>{number = 4, name = (null)} // 线程复用!

重要特性

1. 线程复用

// GCD 自动复用线程,提高性能
// 不会为每个任务都创建新线程

2. 队列层级

// 主队列 - 主线程,UI更新
DispatchQueue.main.async {
    // 更新UI
}

// 全局队列 - 系统提供的并发队列
let globalQueue = DispatchQueue.global(qos: .background)

// 自定义队列
let customQueue = DispatchQueue(label: "custom")

3. 避免线程爆炸

// ❌ 错误:直接创建大量线程
for _ in 0...1000 {
    Thread {
        // 工作
    }.start()
}

// ✅ 正确:使用队列,系统自动管理
let queue = DispatchQueue.global()
for _ in 0...1000 {
    queue.async {
        // 工作 - 系统会合理分配线程
    }
}

最佳实践

1. 选择合适队列

// UI更新用主队列
DispatchQueue.main.async {
    // 更新UI
}

// 后台任务用全局队列
DispatchQueue.global(qos: .userInitiated).async {
    // 耗时任务
    DispatchQueue.main.async {
        // 完成后更新UI
    }
}

2. 避免阻塞主队列

// ❌ 错误:在主队列执行耗时操作
DispatchQueue.main.async {
    let data = processLargeData() // 阻塞UI
    updateUI(data)
}

// ✅ 正确:在后台处理
DispatchQueue.global(qos: .userInitiated).async {
    let data = processLargeData()
    DispatchQueue.main.async {
        updateUI(data)
    }
}

总结

DispatchQueue 是线程的智能调度器

  • 它 管理 任务,让开发者专注于"做什么"
  • 它 隐藏 线程细节,自动优化资源使用
  • 它 保证 任务按照指定顺序和方式执行

GCD 通过 DispatchQueue 将开发者从繁琐的线程管理中解放出来,让并发编程变得更简单、更高效、更安全。

👉 追问:为什么说 GCD 是“基于队列而不是线程”的并发模型?

说 GCD 是"基于队列而不是线程"的并发模型,核心在于它将并发抽象的重心从"谁执行"(线程)转移到了"什么被执行"(任务)以及"如何组织执行"(队列)。

核心思想对比

传统线程模型

// 关注的是"执行者"
let thread1 = Thread { /* 任务A */ }
let thread2 = Thread { /* 任务B */ }
thread1.start()
thread2.start()
// 你需要管理:创建线程、分配任务、协调线程、销毁线程

GCD 队列模型

// 关注的是"任务组织"
let queue = DispatchQueue(label: "work", attributes: .concurrent)
queue.async { /* 任务A */ }  // 我只管提交任务
queue.async { /* 任务B */ }  // 系统决定如何执行
// 你只需要:定义任务、选择队列、提交任务

为什么这个区别很重要?

1. 解耦任务与执行资源

// 传统线程:任务和线程强绑定
Thread {
    downloadImage()  // 任务绑定到这个特定线程
}

// GCD:任务和线程解耦
queue.async {
    downloadImage()  // 任务提交到队列,系统分配线程
}
// 同一个任务在不同时间可能由不同线程执行

2. 从"微观管理"到"宏观调度"

传统线程编程GCD 队列编程
思考:需要多少线程?思考:任务如何组织?
担心:线程创建/销毁开销专注:任务依赖和顺序
操心:线程同步和通信利用:队列的同步特性

3. 编程模型更直观

// 用队列表达执行顺序非常自然

// 串行执行:天然保证顺序
serialQueue.async { task1() }
serialQueue.async { task2() }  // 一定在 task1 之后

// 并发执行:简单明了
concurrentQueue.async { task1() }
concurrentQueue.async { task2() }  // 可能并行执行

// 依赖关系:清晰表达
queue.async {
    let data = fetchData()
    DispatchQueue.main.async {
        updateUI(with: data)
    }
}

实际体现

示例:对比两种模型的复杂性

// 传统线程方式实现三个任务的串行执行
class ThreadManager {
    var currentThread: Thread?
    
    func executeSequentially() {
        let thread1 = Thread {
            task1()
            // 需要手动协调下一个任务
            let thread2 = Thread {
                task2()
                let thread3 = Thread {
                    task3()
                }
                thread3.start()
            }
            thread2.start()
        }
        thread1.start()
    }
}

// GCD 方式实现三个任务的串行执行
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async { task1() }
serialQueue.async { task2() }
serialQueue.async { task3() }
// 简洁明了,自动保证顺序

示例:避免"线程爆炸"

// 传统方式:容易创建过多线程
for i in 0..<1000 {
    Thread {
        processItem(i)
    }.start()  // 可能创建1000个线程!
}

// GCD 方式:系统智能管理
let queue = DispatchQueue.global()
for i in 0..<1000 {
    queue.async {
        processItem(i)  // 系统复用线程池中的线程
    }
}
// 可能只用 8-64 个线程(根据 CPU 核心数优化)

GCD 的哲学转变

1. 关注点分离

// 你关心的:
- 任务是什么?
- 任务间的依赖关系?
- 任务的优先级?
- 执行顺序要求?

// 系统关心的:
- 用多少线程?
- 哪个线程执行哪个任务?
- 何时创建/销毁线程?
- 如何负载均衡?

2. 更高级的抽象

// GCD 提供了高级原语
let group = DispatchGroup()  // 任务组
let semaphore = DispatchSemaphore(value: 1)  // 信号量
let barrier = DispatchWorkItem(flags: .barrier)  // 屏障

// 这些都在队列层面操作,不直接涉及线程

3. 性能优化透明化

// 系统可以根据情况优化:
// - 线程复用
// - 负载均衡
// - CPU 亲和性
// - 能耗优化(iOS 上特别重要)

// 你只需:queue.async { work }
// 系统决定:用哪个核、哪个线程、何时执行

现实类比

传统线程模型:自己建工厂

  • 你需要:招聘工人(创建线程)
  • 分配任务:A 你去拧螺丝,B 你去焊接
  • 协调管理:A 做完告诉 B
  • 成本高:工人闲置时也要付工资

GCD 队列模型:使用云服务平台

  • 你只需:提交任务需求单(任务到队列)
  • 平台负责:分配工人、调度任务、优化资源
  • 你只关心:任务描述和依赖关系
  • 成本优:按需使用,资源复用

总结

GCD 的"基于队列"模型之所以强大,是因为:

  1. 抽象层次更高:从"执行单元"抽象到"任务组织"
  2. 资源管理更优:系统全局优化,避免局部最优
  3. 编程模型更安全:减少了数据竞争和死锁的机会
  4. 性能更可预测:避免了线程爆炸和过度订阅
  5. 代码更简洁:关注业务逻辑,而不是线程管理

这种设计使得并发编程从一门"黑魔法"变成了更可控、更安全的工程实践。你不再直接指挥"士兵"(线程),而是制定"作战计划"(队列和任务),让"指挥部"(GCD)去最优执行。

下一章juejin.cn/post/759983…