DispatchQueue:什么是 DispatchQueue、GCD 队列及多线程基础

作者: IT Sectr 发布日期: 2026-03-16 阅读时间: 8 分钟

DispatchQueue 是 Grand Central Dispatch(GCD)框架中用于管理 iOS 和 macOS 异步任务的基础队列。根据 Apple Developer Documentation, 2026DispatchQueue 通过串行和并发队列将线程管理从开发人员中抽象出来。GCD 自动将任务分配到系统线程池,免去了手动创建和销毁线程的麻烦。

要点

  • DispatchQueue — GCD 在 iOS 中异步执行代码的主要抽象
  • 串行队列严格按顺序执行任务,消除竞态条件
  • 并发队列通过系统线程池并行执行多个任务
  • QoS 设置任务的优先级 — 从 userInteractive 到 background
  • DispatchQueue.main — 在主线程上更新 UIKit 的唯一队列

什么是 DispatchQueue 和 Grand Central Dispatch

DispatchQueue 是 Grand Central Dispatch(GCD)框架的一个对象,管理系统或自定义线程队列中任务的执行。Grand Central Dispatch 是 Apple 的低级库,从 iOS 4 和 macOS 10.6 开始可用,它将线程管理完全从开发人员中抽象出来。GCD 使用操作系统的线程池,并根据设备负载自动扩展线程数量。

开发人员无需手动创建和销毁线程 — GCD 接管这一任务,通过 DispatchQueue 提供简单的 API。任务以闭包(closure)的形式通过 syncasync 方法发送到队列。在第一种情况下,调用线程被阻塞直到任务完成;在第二种情况下,立即继续执行。

根据 Apple(2026)的数据,GCD 使用适应核心数量和当前处理器负载的系统线程池。并发队列不会为每个任务创建新线程 — GCD 复用池中的线程,从而最大限度地减少创建线程的开销。

GCD 架构

Grand Central Dispatch 由三个关键组件组成:队列(DispatchQueue)、(DispatchGroup)和信号量(DispatchSemaphore)。队列是接收代码块形式任务的主要元素。DispatchGroup 同步多个任务的执行,DispatchSemaphore 将共享资源的访问限制在特定数量的线程。

每个 GCD 队列都与特定的 QoS(Quality of Service)类相关联,该类通知系统任务的重要性。系统使用 QoS 在队列之间分配处理器时间,优先处理更关键的任务 — 例如 UI 更新或用户触摸处理。

串行和并发队列:比较

串行队列严格按顺序执行任务,一个接一个。如果将三个任务放入串行队列,第二个任务只有在前一个任务完全完成后才能开始。串行队列用于同步对共享资源的访问 — 例如,从代码的多个部分修改的数组。

并发队列同时执行多个任务,将它们分配到系统池中的可用线程。并发队列中的任务按到达顺序(FIFO)启动,但如果执行时间不同,则按任意顺序完成。并发队列不保证完成的顺序 — 只保证启动的顺序。

参数串行队列并发队列
执行顺序严格按顺序并行
线程数一个多个来自 GCD 池
应用保护共享资源独立计算
Main queue是(主线程)
死锁风险在同一队列上使用 sync 时高

何时选择串行队列

串行队列非常适合修改共享状态的任务 — 写入文件、更新数据模型或使用 Core Data。使用串行队列可确保两段代码不会同时修改相同的数据,无需额外锁即可消除竞态条件。

何时选择并发队列

并发队列适用于相互独立的任务:加载多个图像、并行网络请求或批量数据处理。GCD 根据处理器核心数量和当前系统负载自动决定同时运行多少任务。

Quality of Service:任务执行优先级

QoS(Quality of Service)— GCD 的机制,通知操作系统任务的重要性和紧迫性。系统使用 QoS 进行线程调度:具有更高 QoS 的任务获得更多的处理器时间并更早启动。QoS 值在创建队列或发送特定任务时传递。

GCD 中有五个 QoS 类。.userInteractive — 与 UI 相关的任务的最高优先级。.userInitiated — 用于用户启动的任务。.utility — 用于显示进度的后台任务。.background — 用于用户不可见的任务。.default — userInitiated 和 utility 之间的中间级别,默认使用。

根据 Apple(2026)的数据,错误的 QoS 选择是性能问题的常见原因之一。使用 QoS .userInteractive 运行后台下载会从 UI 中夺取资源,导致动画中的微卡顿。建议选择仍然提供可接受执行时间的最低 QoS。

QoS 应用示例

在加载图像以立即显示时,使用 .userInitiated — 用户期望结果。对于预加载下一屏幕,.utility 就足够了。与服务器的后台同步使用 .background 执行,最大限度地减少对活动任务的影响。

DispatchGroup 和信号量:任务同步

DispatchGroup 允许跟踪一组任务的完成情况。当组中的所有任务完成时,GCD 在指定队列上调用 notify 处理程序。这在加载多个独立资源 — 配置文件数据、好友列表和设置 — 时特别有用,因为界面需要在收到所有数据后才能更新。

DispatchGroup 支持同步调用 wait(),该调用阻塞当前线程直到所有任务完成。这在代码无法在没有组结果的情况下继续时很方便。异步变体 — notify() — 在所有任务完成后在指定队列上调用闭包,而不会阻塞调用线程。

使用 DispatchSemaphore 限制并行

DispatchSemaphore 控制对资源的访问,限制同时访问的次数。初始值为 3 的信号量允许运行不超过三个并行任务。调用 wait() 时计数器减少,调用 signal() 时增加。如果计数器为零,线程被阻塞直到资源释放。

Swift 中 DispatchQueue 的代码示例

我们来看三个在 Swift 中使用 DispatchQueue 的实际示例。第一个演示基本的 async 调用并返回主线程,第二个 — 通过串行队列进行同步,第三个 — DispatchGroup 用于并行请求。

基本的 async 调用并返回 main

DispatchQueue.main 是主线程的串行队列,专用于 UI 操作。在后台工作完成后,始终使用它来更新界面。

swift
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
    let data = self.fetchData()
    DispatchQueue.main.async {
        self.updateUI(with: data)
    }
}

用于保护共享资源的串行队列

使用唯一标识符创建自己的串行队列可以同步对可变数组的访问。所有读写操作都通过一个队列,消除竞态条件。

swift
let serialQueue = DispatchQueue(label: "com.app.items")
var items: [Int] = []

serialQueue.async {
    items.append(1)
}
serialQueue.async {
    let last = items.last
    DispatchQueue.main.async {
        print("Last item: \(last)")
    }
}

用于并行请求的 DispatchGroup

DispatchGroup 允许在并发队列上启动多个任务,并在所有任务完成时收到通知。这在加载配置文件屏幕数据时很有用。

swift
let group = DispatchGroup()
let worker = DispatchQueue.global()

worker.async(group: group) { self.loadProfile() }
worker.async(group: group) { self.loadFriends() }
worker.async(group: group) { self.loadSettings() }

group.notify(queue: DispatchQueue.main) {
    self.showCompleteUI()
}

使用 DispatchQueue 时的常见错误

死锁 — 在串行队列上调用 sync 时最常见的错误。如果串行队列上的任务在同一个队列上调用 queue.sync,线程将永久阻塞。队列等待当前任务完成,而任务等待 sync 调用完成 — 经典的互锁。

从后台线程更新 UI

所有 UIKit 操作必须在主线程上执行。Xcode 在 Debug 模式下通过 Main Thread Checker 检测这些错误。在 Release 版本中,它们会导致不可预测的行为:动画不启动,UI 不更新,可能崩溃。

过度创建自定义队列

创建数百个自定义队列而不是全局队列是一种反模式。每个队列都会消耗系统资源。对于大多数任务,具有不同 QoS 的全局并发队列和用于同步共享数据的一两个串行队列就足够了。

在循环中忽略 autoreleasepool

在没有 autoreleasepool 的后台队列上执行资源密集型循环任务时,内存会持续增长直到整个循环结束。ARC 仅在退出 autorelease pool 时释放对象。将循环迭代包装在 autoreleasepool { } 中以及时释放内存。

常见问题

DispatchQueue 和 OperationQueue 有什么区别?

OperationQueue 构建在 GCD 之上,但提供了更高级别的 API,具有操作依赖关系、KVO 和取消支持。DispatchQueue 是用于简单异步任务的低级队列,没有依赖管理。

能否强制停止 DispatchQueue 中的任务?

GCD 不支持停止正在运行的任务。suspend() 方法仅暂停新任务,当前任务运行到底。取消需要在任务代码内手动检查标志。

为网络请求选择什么 QoS?

对于需要立即显示结果的主要请求 — .userInitiated。对于预加载数据 — .utility。对于后台同步 — .background

并发队列使用多少个线程?

GCD 不固定线程数量。线程池根据负载动态扩展,考虑处理器核心、当前负载和每个任务的 QoS。最大数量受系统限制。

为什么 DispatchQueue.main 对 UIKit 是必需的?

UIKit 不是线程安全的 — 它的所有类必须仅从主线程调用。违反会导致不可预测的行为、更新丢失和生产中的崩溃。

总结

  • DispatchQueue — Grand Central Dispatch 在 iOS 和 macOS 中用于异步任务的主要工具
  • 串行队列按顺序执行任务,无需锁即可消除竞态条件
  • 并发队列通过系统线程池并行执行任务
  • QoS 确定任务的优先级 — 从 userInteractive 到 background
  • DispatchGroup 通过 notify 在主线程上同步多个并行任务
  • 死锁在繁忙的串行队列上使用 sync 时 — 需要注意的关键错误
  • 主线程对 UIKit 是必需的 — 仅通过 DispatchQueue.main 更新界面

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读