编程移动开发者

解释 GCD(Grand Central Dispatch)和 Swift 中的 DispatchQueue 的工作原理,如何正确创建异步代码,以及在多线程工作中可能遇到的陷阱?

用 Hintsage AI 助手通过面试

答案.

问题的背景

Grand Central Dispatch(GCD)于 2009 年进入 iOS 和 macOS,作为一种低级别的系统,用于组织竞争性和异步代码(多线程),基于队列——DispatchQueue。GCD 的实现比手动管理线程更加简洁,提供了安全执行任务、同步和方便的 API。

问题

异步和多线程通常是许多问题的源头:数据竞争、死锁、界面假死和复杂的崩溃。不正确使用队列或尝试从非主线程访问 UI 会导致错误。

解决方案

Swift 允许轻松创建后台任务,并安全返回主线程以更新 UI,使用全局和自定义队列。使用 DispatchQueue 进行异步工作,使用 DispatchGroup 如果需要等待一组异步任务的完成。

代码示例:

let backgroundQueue = DispatchQueue(label: "background", qos: .background) backgroundQueue.async { // 在后台线程执行 let image = downloadImage() DispatchQueue.main.async { // 安全更新 UI imageView.image = image } }

关键特点:

  • DispatchQueue.async 异步执行代码块
  • DispatchQueue.main.async 用于在主线程调用
  • DispatchSemaphore,DispatchGroup,sync,以及 qos 用于控制任务优先级

陷阱问题。

如果在主线程中从主队列调用 sync,会发生什么?

将发生死锁:主线程正在等待在其自身上执行的任务,因此应用程序将“冻结”。

DispatchQueue.main.sync { // 死锁 }

DispatchQueue.serial 可以并行执行任务吗?

不能,serial 队列总是依次执行任务,然而如果创建多个 serial 队列,它们可以相互并行执行。

允许在非主线程中更新 UI 吗?

不可以,任何对 UIKit(或 SwiftUI View 渲染)的操作只能在 DispatchQueue.main 中进行。违反此规则会导致不稳定的操作和崩溃。

常见错误和反模式

  • 在主线程上的同步调用(死锁)
  • 尝试从后台更新 UI
  • 在公共变量写入时未管理的数据竞争
  • 无需使用全局队列,缺乏专用队列

生活示例

负面案例

在后台线程加载数据,同时更新 UI——应用程序有时崩溃或界面假死。此外,在线程之间使用共享可变状态而没有同步。

优点:

  • 代码在视觉上简洁

缺点:

  • 不稳定的错误和难以重现的崩溃
  • 性能损失

正面案例

所有 UI 更新仅在 DispatchQueue.main 上组织,为处理大数据提供专用队列,使用 DispatchGroup 控制异步任务的完成。

优点:

  • 没有竞争
  • 有效分割任务
  • 维护简单

缺点:

  • 在线程之间的“翻转”很多,处理共享资源时需要纪律