You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义串行DispatchQueue未死锁且复用主线程的原因解析

GCD串行队列同步任务的线程复用与死锁问题

问题背景

我编写了一段自定义串行队列的GCD代码,运行后发现同步任务居然在主线程执行且未死锁,但将队列换成主队列时却出现了预期中的死锁,有两个疑问:

  1. 为什么自定义串行队列用sync提交任务时,与当前线程(主线程)相同却未发生死锁?
  2. 为什么自定义串行队列会复用主线程执行任务,我原本以为它会使用其他线程?

代码示例与输出

示例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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 06:15:13