Kotlin 协程线程切换原理 ------ 以 Dispatchers.IO 为例
目录
- 核心结论
- 关键角色
- [launch 启动全流程](#launch 启动全流程 "#launch-%E5%90%AF%E5%8A%A8%E5%85%A8%E6%B5%81%E7%A8%8B")
- 关键源码逐层拆解
- [delay 之后为何还在 IO 线程](#delay 之后为何还在 IO 线程 "#delay-%E4%B9%8B%E5%90%8E%E4%B8%BA%E4%BD%95%E8%BF%98%E5%9C%A8-io-%E7%BA%BF%E7%A8%8B")
- 完整时序图
- [DispatchedContinuation 是核心](#DispatchedContinuation 是核心 "#dispatchedcontinuation-%E6%98%AF%E6%A0%B8%E5%BF%83")
- [Dispatchers.IO vs Dispatchers.Default](#Dispatchers.IO vs Dispatchers.Default "#dispatchersio-vs-dispatchersdefault")
核心结论
launch(Dispatchers.IO)的线程切换,本质是把协程体包装成一个Runnable,然后post到目标线程池的任务队列 。主线程调用launch后立即返回,协程体由 IO 线程池的某个线程从队列中取出并执行。没有任何神奇的黑盒,就是一次
executor.submit(runnable)。
关键角色
| 类 | 职责 |
|---|---|
CoroutineDispatcher |
线程切换的抽象,实现 ContinuationInterceptor |
Dispatchers.IO |
共享 IO 线程池(最多 64 个线程) |
DispatchedContinuation |
把 Continuation 包装成 Runnable,投递到目标线程 |
ContinuationInterceptor |
协程上下文中的拦截器,在协程启动时负责包装 Continuation |
StandaloneCoroutine |
launch 创建的协程对象,持有 CoroutineContext |
markdown
CoroutineContext
└── Dispatchers.IO(实现了 ContinuationInterceptor)
└── 拦截 Continuation,包装成 DispatchedContinuation
└── 投递到 IO 线程池
launch 启动全流程
以下面这段代码为例:
kotlin
// 主线程点击事件
lifecycleScope.launch(Dispatchers.IO) {
log("thread = ${Thread.currentThread()} >>> delay: 开始") // IO 线程
delay(1000)
log("thread = ${Thread.currentThread()} >>> delay: 结束") // 仍在 IO 线程
}
分 5 步拆解
Step 1:launch 合并 CoroutineContext
kotlin
// kotlinx/coroutines/Builders.kt
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
// 合并:lifecycleScope 自带的 Context(Main Dispatcher)+ 传入的 Dispatchers.IO
// Dispatchers.IO 会覆盖父级的 Dispatcher
val newContext = newCoroutineContext(context)
// 最终 newContext 中的 Dispatcher = Dispatchers.IO
val coroutine = StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
Step 2:coroutine.start() 触发拦截器
kotlin
// AbstractCoroutine.start()
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
└── block.startCoroutineCancellable(coroutine)
└── createCoroutineUnintercepted(...) // 创建原始 Continuation
.intercepted() // ← 关键!触发拦截器
.resumeCancellableWith(Result.success(Unit))
Step 3:intercepted() 包装成 DispatchedContinuation
kotlin
// ContinuationImpl.intercepted()
public fun intercepted(): Continuation<Any?> =
intercepted ?: (
// 从 Context 取出 ContinuationInterceptor(即 Dispatchers.IO)
context[ContinuationInterceptor]
?.interceptContinuation(this) // 让 Dispatcher 包装自己
?: this
).also { intercepted = it }
// CoroutineDispatcher.interceptContinuation()
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
// 把原始 Continuation 包进 DispatchedContinuation(一个 Runnable)
Step 4:dispatch() 投递到 IO 线程池
kotlin
// DispatchedContinuation.resumeCancellableWith()
override fun resumeCancellableWith(result: Result<T>) {
val state = result.toState()
// 判断是否需要 dispatch(当前线程 ≠ 目标线程时,需要)
if (dispatcher.isDispatchNeeded(context)) {
this.result = state
// 把自己(Runnable)投递到 Dispatchers.IO 的线程池
dispatcher.dispatch(context, this)
// ↑ Dispatchers.IO 把这个 Runnable 扔进 IO 线程池队列
} else {
// 已经在目标线程,直接执行(优化路径)
executeUnconfined(state, MODE_CANCELLABLE)
}
}
Step 5:IO 线程取出任务,执行协程体
kotlin
// IO 线程池的某个线程取出任务:
DispatchedContinuation.run() // Runnable.run()
└── continuation.resumeWith(result) // 恢复 Continuation
└── invokeSuspend(Unit) // 进入协程状态机
└── 执行协程体(此时已在 IO 线程)
log("thread = IO线程")
delay(1000) ...
关键源码逐层拆解
DispatchedContinuation ------ 线程切换的载体
kotlin
// kotlinx/coroutines/internal/DispatchedContinuation.kt
internal class DispatchedContinuation<in T>(
val dispatcher: CoroutineDispatcher, // 持有目标 Dispatcher(IO)
val continuation: Continuation<T> // 持有原始协程 Continuation
) : DispatchedTask<T>(MODE_UNINITIALIZED),
CoroutineStackFrame,
Continuation<T> by continuation // 代理原始 Continuation 的接口
{
// 实现 Runnable.run(),由线程池线程调用
override fun run() {
val taskContext = this.taskContext
var fatalException: Throwable? = null
try {
val delegate = delegate as DispatchedContinuation<T>
val continuation = delegate.continuation
withContinuationContext(continuation, delegate.countOrElement) {
val context = continuation.context
val state = takeState()
val exception = getExceptionalResult(state)
val job = if (exception == null && job != null) context[Job] else null
if (job != null && !job.isActive) {
// 协程已取消,不执行
val cause = job.getCancellationCause()
continuation.resumeWithStackTrace(JobCancellationException(...))
} else {
// ← 就在这里,IO 线程调用 resumeWith,驱动状态机
continuation.resumeWith(
if (exception != null) Result.failure(exception)
else Result.success(getSuccessfulResult(state))
)
}
}
} catch (e: Throwable) { ... }
}
}
Dispatchers.IO 线程池实现
kotlin
// IO Dispatcher 底层是一个弹性线程池
// 默认最大线程数 = max(64, CPU核心数)
// 可通过 kotlinx.coroutines.io.parallelism 系统属性调整
public val IO: CoroutineDispatcher = DefaultScheduler.IO
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
val IO = LimitingDispatcher(
this,
systemProp(IO_PARALLELISM_PROPERTY_NAME, 64), // 最多 64 线程
"Dispatchers.IO",
TASK_PROBABLY_BLOCKING // 标记为"可能阻塞"的任务类型
)
}
// dispatch() 实现:把 DispatchedContinuation(Runnable)扔进线程池
override fun dispatch(context: CoroutineContext, block: Runnable) {
DefaultScheduler.dispatchWithContext(block, this, false)
// ↑ 最终调用底层 CoroutineScheduler.dispatch()
// 把 block 加入工作队列,等待空闲线程取走执行
}
delay 之后为何还在 IO 线程
delay 挂起后恢复时,协程会被再次派发(dispatch),派发用的 Dispatcher 是 Continuation 上下文里绑定的那个 ,也就是启动时指定的 Dispatchers.IO。
kotlin
lifecycleScope.launch(Dispatchers.IO) {
// ① 此处在 IO 线程(dispatch 到 IO 线程池)
log("${Thread.currentThread()}")
delay(1000)
// delay 挂起,1000ms 后恢复时:
// scheduleResumeAfterDelay → 定时器到期 → cont.resumeWith()
// cont 的 context 中 Dispatcher = Dispatchers.IO
// → 再次 dispatcher.dispatch(context, this)
// → 再次投递到 IO 线程池
// ② 此处仍在 IO 线程(不一定是同一个线程,但一定是 IO 线程池的线程)
log("${Thread.currentThread()}")
}
关键代码在 CancellableContinuationImpl 恢复时:
kotlin
// CancellableContinuationImpl.resumeImpl()
private fun resumeImpl(proposedUpdate: Any?, resumeMode: Int, ...) {
...
// dispatchResume 内部会调用 dispatcher.dispatch()
// dispatcher 就是协程 Context 里的 Dispatchers.IO
dispatchResume(resumeMode)
}
完整时序图
sequenceDiagram
autonumber
participant M as 主线程 (Main)
participant F as 协程框架
participant IO as IO 线程池
participant T as 定时器线程
M->>F: lifecycleScope.launch(Dispatchers.IO) { ... }
Note over F: 1. 合并 Context,Dispatchers.IO 覆盖父 Dispatcher
2. 创建 StandaloneCoroutine
3. 调用 block.startCoroutineCancellable() F->>F: continuation.intercepted() Note over F: Dispatchers.IO.interceptContinuation(cont)
→ 包装成 DispatchedContinuation(IO, cont) F->>IO: dispatcher.dispatch(context, dispatchedContinuation) Note over IO: 把 DispatchedContinuation(Runnable)
投入 IO 线程池任务队列 M-->>M: launch() 立即返回,主线程不阻塞 ✓ Note over IO: IO 线程空闲,取出任务 IO->>IO: DispatchedContinuation.run() IO->>IO: continuation.resumeWith(Unit) IO->>IO: invokeSuspend(Unit) [label=0] Note over IO: 执行协程体,此时在 IO 线程
log(">>> delay: 开始") IO->>T: delay(1000, continuation)
scheduleResumeAfterDelay(1000ms, cont) Note over T: cont 绑定了 Dispatchers.IO
保存 cont,注册定时器 T-->>IO: return COROUTINE_SUSPENDED Note over IO: IO 线程释放 ✓ Note over T: ⏱ 1000ms 后,定时器到期 T->>F: cont.resumeWith(success(Unit)) Note over F: 检查 cont 的 Context
Dispatcher = Dispatchers.IO
→ 需要 dispatch! F->>IO: dispatcher.dispatch(context, dispatchedCont) Note over IO: 再次投入 IO 线程池队列 Note over IO: IO 线程取出任务 IO->>IO: invokeSuspend(success) [label=1] Note over IO: 从断点继续,仍在 IO 线程
log(">>> delay: 结束") ✓
2. 创建 StandaloneCoroutine
3. 调用 block.startCoroutineCancellable() F->>F: continuation.intercepted() Note over F: Dispatchers.IO.interceptContinuation(cont)
→ 包装成 DispatchedContinuation(IO, cont) F->>IO: dispatcher.dispatch(context, dispatchedContinuation) Note over IO: 把 DispatchedContinuation(Runnable)
投入 IO 线程池任务队列 M-->>M: launch() 立即返回,主线程不阻塞 ✓ Note over IO: IO 线程空闲,取出任务 IO->>IO: DispatchedContinuation.run() IO->>IO: continuation.resumeWith(Unit) IO->>IO: invokeSuspend(Unit) [label=0] Note over IO: 执行协程体,此时在 IO 线程
log(">>> delay: 开始") IO->>T: delay(1000, continuation)
scheduleResumeAfterDelay(1000ms, cont) Note over T: cont 绑定了 Dispatchers.IO
保存 cont,注册定时器 T-->>IO: return COROUTINE_SUSPENDED Note over IO: IO 线程释放 ✓ Note over T: ⏱ 1000ms 后,定时器到期 T->>F: cont.resumeWith(success(Unit)) Note over F: 检查 cont 的 Context
Dispatcher = Dispatchers.IO
→ 需要 dispatch! F->>IO: dispatcher.dispatch(context, dispatchedCont) Note over IO: 再次投入 IO 线程池队列 Note over IO: IO 线程取出任务 IO->>IO: invokeSuspend(success) [label=1] Note over IO: 从断点继续,仍在 IO 线程
log(">>> delay: 结束") ✓
DispatchedContinuation 是核心
flowchart TD
A(["主线程\nlaunch(Dispatchers.IO)"])
B["创建 StandaloneCoroutine\n合并 CoroutineContext"]
C["continuation.intercepted()\nDispatchers.IO.interceptContinuation(cont)"]
D["DispatchedContinuation\n= 原始 Continuation 包了一层 Runnable"]
E["dispatcher.dispatch()\n投递到 IO 线程池队列"]
F(["主线程立即返回 ✓"])
G["IO 线程取出任务\nDispatchedContinuation.run()"]
H["continuation.resumeWith(Unit)\ninvokeSuspend 开始执行"]
I(["协程体运行在 IO 线程 ✓"])
A --> B --> C --> D --> E
E --> F
E --> G --> H --> I
style A fill:#4A90D9,color:#fff,stroke:none
style F fill:#27AE60,color:#fff,stroke:none
style I fill:#27AE60,color:#fff,stroke:none
style D fill:#E8A838,color:#fff,stroke:none
style E fill:#8E44AD,color:#fff,stroke:none
Dispatchers.IO vs Dispatchers.Default
| 对比项 | Dispatchers.Default |
Dispatchers.IO |
|---|---|---|
| 线程池 | 共享的 CoroutineScheduler |
共享的 CoroutineScheduler(同一个!) |
| 最大线程数 | CPU 核心数(通常 4~8) | max(64, CPU核心数) |
| 任务类型标记 | TASK_NON_BLOCKING |
TASK_PROBABLY_BLOCKING |
| 适用场景 | CPU 密集运算 | 阻塞型 I/O(网络、文件、数据库) |
dispatch() 实现 |
投入同一调度器,限制并发数 | 投入同一调度器,允许更多并发 |
两者底层用的是同一个
CoroutineScheduler,Dispatchers.IO只是放开了最大并发限制,允许更多线程同时阻塞等待 I/O。
三句话总结
| 步骤 | 发生了什么 |
|---|---|
| launch | 把协程体(Continuation)用 DispatchedContinuation 包装成 Runnable,post 到 IO 线程池 |
| 协程体执行 | IO 线程从队列取出 Runnable,调用 run() → resumeWith() → invokeSuspend(),协程体在 IO 线程运行 |
| delay 后恢复 | 定时器触发后,再次通过 Dispatchers.IO.dispatch() 投递到 IO 线程池,协程仍在 IO 线程继续 |