基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

GCD 中 dispatch_sync 和 dispatch_async 的区别?

知识点图片

在 GCD (Grand Central Dispatch) 中,dispatch_syncdispatch_async 的核心区别在于 “是否等待任务执行完毕” 以及 “是否阻塞当前线程”

简单总结:

  • dispatch_sync (同步提交): “做完再走”。阻塞当前线程,直到 Block 中的任务执行完毕。
  • dispatch_async (异步提交): “交给你了,我先走”。不阻塞当前线程,函数立即返回,任务在稍后执行。

以下是详细的对比分析:

1. 是否阻塞当前线程 (Blocking)

这是两者最本质的区别:

  • dispatch_sync:

    • 阻塞。调用它的线程会被挂起(等待),直到被提交到队列的任务执行结束,dispatch_sync 函数才会返回,代码才会继续往下执行。
    • 意味着:任务的执行顺序是确定的,必须等这一步做完才能做下一步。
  • dispatch_async:

    • 不阻塞。调用它的线程不会等待,dispatch_async 函数会立即返回,代码继续往下执行。被提交的任务会在未来的某个时间点由 GCD 安排执行。
    • 意味着:你无法确定任务什么时候开始,也无法确定它什么时候结束(除非使用 Group 或 Semaphore)。

2. 是否开启新线程 (Threading)

虽然 syncasync 主要描述的是提交方式,但它们对线程的使用也有不同倾向:

  • dispatch_sync:

    • 通常不开启新线程。为了优化性能,GCD 通常会直接在当前线程中执行该任务(即“借用”当前线程)。
    • 例外:如果提交到主队列(Main Queue),它必须在主线程跑;如果提交到其他特定队列,逻辑上它是在那个队列跑,但物理上通常还是阻塞当前线程并利用当前线程执行。
  • dispatch_async:

    • 具备开启新线程的能力
    • 如果提交到并发队列 (Concurrent Queue)全局队列 (Global Queue),GCD 通常会从线程池中取出一个新线程来执行任务。
    • 如果提交到主队列 (Main Queue),任务依然会在主线程执行(因为主队列是串行的且绑定主线程),但它会在下一次 RunLoop 循环中执行,不会卡住当前的执行流。

3. 常见场景与组合 (Queue + Dispatch)

理解它们的行为需要结合队列类型(串行 Serial / 并发 Concurrent):

组合方式 是否开启新线程 执行方式 备注
Async + 并发队列 并发执行 最常用的后台任务处理方式(如下载、计算)。
Async + 串行队列 (通常开启一条) 串行执行 任务一个接一个执行,不阻塞当前线程。
Async + 主队列 串行执行 最常用。用于从后台线程切回主线程刷新 UI。
Sync + 并发队列 串行执行 在当前线程执行。常用于实现读写锁的“读”操作。
Sync + 串行队列 串行执行 在当前线程执行。
Sync + 主队列 死锁 (Deadlock) 如果在主线程调用会死锁

4. 致命陷阱:死锁 (Deadlock)

这是面试和开发中最重要的考点。

场景:在当前串行队列中,使用 dispatch_sync同一个串行队列提交任务。

最典型的例子(主线程死锁)

plaintext
override func viewDidLoad() {
    super.viewDidLoad()
    
    print("1")
    // 主线程正在执行 viewDidLoad
    // 主队列是串行队列
    dispatch_sync(dispatch_get_main_queue()) {
        print("2")
    }
    print("3")
}

结果:程序崩溃(死锁),只会打印 "1"。

原因分析

  1. viewDidLoad 在主线程(主队列)执行。
  2. 遇到 dispatch_sync,主线程被阻塞,等待 Block 中的任务执行完毕。
  3. Block 被提交到了主队列,准备执行。
  4. 但是,主队列是串行的(FIFO),它必须等当前正在执行的任务(即 viewDidLoad)执行完,才能执行下一个任务(即 Block)。
  5. 互相等待:主线程在等 Block 执行完(因为 sync),Block 在等 viewDidLoad 执行完(因为串行队列)。导致死锁。

5. 总结与应用建议

  • 使用 dispatch_async 的场景

    • 耗时操作(网络请求、图片处理、大文件读写)。
    • UI 刷新(DispatchQueue.main.async)。
    • 不想卡住当前界面的时候。
  • 使用 dispatch_sync 的场景

    • 非常少用,需谨慎。
    • 必须等待某个任务有了结果才能继续往下走(且不想用回调闭包时)。
    • 线程安全:用于在并发队列中实现同步读取数据(配合 Barrier 实现读写分离)。
    • 注意:永远不要在主线程对主队列使用 sync

一句话记忆:
sync 是“我等你做完”,async 是“你去做吧,我不等”。

00:00
00:00