自定义串行DispatchQueue未死锁且复用主线程的原因解析
GCD串行队列同步任务的线程复用与死锁问题
问题背景
我编写了一段自定义串行队列的GCD代码,运行后发现同步任务居然在主线程执行且未死锁,但将队列换成主队列时却出现了预期中的死锁,有两个疑问:
- 为什么自定义串行队列用
sync提交任务时,与当前线程(主线程)相同却未发生死锁? - 为什么自定义串行队列会复用主线程执行任务,我原本以为它会使用其他线程?
代码示例与输出
示例1:自定义串行队列(无死锁)
let serialQueue = DispatchQueue(label: "com.hello") print("current:",Thread.current) for i in 0...9 { serialQueue.sync { print(i,Thread.current) } } print("hello world")
运行输出:
current: <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 0 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 1 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 2 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 3 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 4 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 5 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 6 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 7 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 8 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} 9 <NSThread: 0x7f8dd5c0dfe0>{number = 1, name = main} hello world
示例2:主队列(死锁)
let serialQueue = DispatchQueue.main print("current:",Thread.current) for i in 0...9 { serialQueue.sync { print(i,Thread.current) } } print("hello world")
解答
1. 自定义串行队列无死锁的原因
GCD的死锁触发条件是:在某个串行队列的执行线程上,同步提交任务到同一个串行队列。
- 主队列是特殊的串行队列,它的执行线程固定为主线程。当你在主线程调用
DispatchQueue.main.sync时,主线程会阻塞等待提交的任务执行,但这个任务被排在主队列当前正在执行的任务之后,永远无法得到执行机会,因此死锁。 - 自定义串行队列没有绑定固定的执行线程。当你用
sync提交任务时,GCD会检查:当前线程是否正在执行该自定义队列的任务?如果没有,就直接在当前线程(主线程)执行提交的闭包,不需要等待队列调度,自然不会出现死锁。
2. 自定义队列复用主线程的原因
sync方法的核心是同步等待任务完成,GCD为了减少线程切换的性能开销,会做「线程合并」优化:
- 当调用
sync的线程未与目标串行队列绑定(自定义队列无固定线程),且目标队列当前没有正在执行的任务时,GCD会直接在当前线程执行任务,而不会调度到线程池的其他线程。 - 注意:这种优化仅针对
sync提交的任务,async提交的任务仍会被调度到线程池的其他线程执行(主队列的async除外)。
内容的提问来源于stack exchange,提问作者Li Fumin
相关产品推荐
相关产品推荐

