关于DispatchQueue.sync()在自定义串行队列的执行线程与无死锁逻辑的疑问
Hey there! Great question—this is one of those nuanced GCD behaviors that confuses even experienced iOS/macOS developers, so let’s break it down step by step.
1. 为什么提交到自定义串行队列的块会在主线程执行?
这是GCD的**当前线程优化(Current Thread Optimization, CTO)**在起作用,是sync()方法内置的性能优化手段。具体逻辑是:
当你调用queue.sync(execute:)时,GCD会先检查两个条件:
- 目标队列是否为主队列?
- 当前线程是否正在执行该目标队列的任务?
如果两个条件都不满足(你的场景正好符合:目标是自定义串行队列,且主线程从未执行过该队列的任务),GCD会跳过线程池调度环节,直接在当前线程(主线程)执行块代码,以此避免线程切换带来的性能开销。
文档里提到的"this function executes blocks on the current thread whenever possible",指的就是这个优化逻辑。
2. 为什么这种情况没有触发死锁?
你的死锁推理逻辑看似合理,但核心假设错误:主线程并没有真正"阻塞等待队列调度任务"。我们需要区分两种完全不同的场景:
什么情况会触发死锁?
死锁的核心是"互相等待":
- 线程A阻塞,等待某个任务完成;
- 该任务必须在线程A上执行,但线程A已被阻塞无法处理。
典型例子是在主线程调用DispatchQueue.main.sync():
- 主线程调用
main.sync()后进入阻塞状态,等待块执行完成; - 块被添加到主队列的任务列表末尾;
- 但主线程正卡在
sync()调用上,无法处理主队列的下一个任务,最终形成死锁。
你的代码为什么没有死锁?
当你在主线程调用serialQueue.sync()时:
- GCD触发了当前线程优化,并没有把块添加到自定义串行队列的等待列表,也没有让主线程"等待队列调度任务",而是直接在
sync()调用内部执行了块的代码。
可以把这个过程理解为:sync()调用并没有"阻塞主线程等待队列执行任务",而是像调用普通函数一样,直接在主线程同步执行块代码,执行完成后sync()调用返回,流程继续。整个过程没有"等待自身执行另一个任务"的环节,自然不会死锁。
3. 主线程在这个场景下的具体运作逻辑
主线程的核心是RunLoop循环,它会持续处理各类事件(UI触摸、定时器、主队列任务等)。但在当前线程优化的场景下,GCD完全绕过了RunLoop机制:
sync()调用会把块代码内联同步执行在主线程,和普通函数调用没有本质区别;- 主线程不需要从队列中"取出另一个任务"处理,块代码就是
sync()调用执行流程的一部分; - 块执行完成后,
sync()调用返回,主线程回到正常的RunLoop循环流程。
只有当主线程"阻塞且等待自身处理队列任务"时,才会出现无法处理其他任务的情况(比如主队列死锁场景)。而你的场景中没有"等待队列任务"的环节,只是直接的内联执行。
验证逻辑的小测试
如果我们修改代码,强制让当前线程优化不生效,就能看到完全不同的行为:
let serialQueue = DispatchQueue(label: "serial.queue") // 先让自定义队列异步执行一个耗时任务 serialQueue.async { Thread.sleep(forTimeInterval: 2) } // 短暂等待确保异步任务已经启动 Thread.sleep(forTimeInterval: 0.1) // 再从主线程调用sync serialQueue.sync { print("current thread = \(Thread.current)") }
此时自定义队列正忙着执行异步任务,GCD无法触发当前线程优化,主线程会阻塞等待自定义队列完成当前任务,之后块会在GCD线程池的后台线程执行,不会死锁,但执行线程不再是主线程。
如果我们再进一步,在自定义队列的任务内部调用sync():
let serialQueue = DispatchQueue(label: "serial.queue") serialQueue.sync { // 此时我们正运行在自定义队列的线程上 serialQueue.sync { print("这段代码会触发死锁!") } }
这时候就会出现死锁:自定义队列是串行的,内层sync()会把块添加到队列的等待列表,当前线程(执行外层块的线程)会阻塞等待内层块执行,但内层块只能在同一个线程执行,而该线程已被阻塞,形成经典的死锁场景。
核心总结
- 当前线程优化是块在主线程执行的原因:GCD会在安全的前提下避免线程切换;
- 无死锁的本质是块内联执行:
sync()调用没有等待队列调度任务,而是直接在当前线程执行块代码; - 主线程RunLoop被绕过:块不是RunLoop中的独立任务,而是
sync()调用执行流程的一部分。
内容来源于stack exchange

