为何DispatchQueue.main.async内调用main.sync死锁,自定义串行队列sync则不会?
主线程调用不同串行队列sync的死锁差异解析
一、为什么调用DispatchQueue.main.sync会死锁?
先看死锁代码:
DispatchQueue.main.async { DispatchQueue.main.sync { print("this won't print") } }
原因很直接:
- 外层
main.async把闭包加到主线程队列末尾,等主线程空闲时执行。 - 当这个外层闭包在主线程启动后,我们调用了
main.sync——sync的规则是阻塞当前线程,直到传入的闭包在目标队列执行完毕。 - 但
DispatchQueue.main是串行队列,任务必须按顺序执行。此时主线程正忙着执行外层的async闭包,sync传入的闭包只能被加到main队列的末尾,排在当前任务之后。 - 这就形成了死锁:主线程被
sync阻塞,等待末尾的闭包执行;而末尾的闭包又要等当前正在执行的外层闭包结束才能开始——两边互相卡住,谁也动不了。
二、为什么自定义串行队列的sync不会死锁?
再看无死锁的代码:
let customSerialQueue = DispatchQueue(label: "com.example.serialqueue") DispatchQueue.main.async { // main thread customSerialQueue.sync { print("this will print") // main thread } }
你通过断点确认闭包在主线程执行,这是GCD的线程复用优化,但核心原因不在复用本身,而是自定义队列的执行状态:
- 调用
customSerialQueue.sync时,这个自定义串行队列当前没有任何任务在执行(刚创建的队列,之前没跑过任务)。 - GCD对串行队列的
sync调用有个关键逻辑:如果目标串行队列当前没有活跃任务,就会直接在当前调用线程(这里就是主线程)上执行传入的闭包,不会把闭包加到队列里排队。 - 这种情况下,
sync不需要阻塞等待队列的任务——闭包直接就在当前线程同步执行,执行完sync就结束,外层的async闭包也能正常完成,完全没有阻塞冲突。
两者的核心区别
- 针对
main队列的sync:目标队列当前正在执行任务(外层的async闭包),sync的闭包只能排队,当前线程阻塞等待排队任务,形成死锁。 - 针对自定义串行队列的
sync:目标队列当前无活跃任务,GCD直接在当前线程执行闭包,sync等待的就是当前线程上的闭包执行,执行完就释放,没有排队等待的冲突。
内容的提问来源于stack exchange,提问作者shinhong
相关产品推荐
相关产品推荐

