Kotlin 协程线程切换原理 —— 以 Dispatchers.IO 为例

117 阅读4分钟

Kotlin 协程线程切换原理 —— 以 Dispatchers.IO 为例

目录

  1. 核心结论
  2. 关键角色
  3. launch 启动全流程
  4. 关键源码逐层拆解
  5. delay 之后为何还在 IO 线程
  6. 完整时序图
  7. DispatchedContinuation 是核心
  8. Dispatchers.IO vs Dispatchers.Default

核心结论

launch(Dispatchers.IO) 的线程切换,本质是把协程体包装成一个 Runnable,然后 post 到目标线程池的任务队列。主线程调用 launch 后立即返回,协程体由 IO 线程池的某个线程从队列中取出并执行。

没有任何神奇的黑盒,就是一次 executor.submit(runnable)


关键角色

职责
CoroutineDispatcher线程切换的抽象,实现 ContinuationInterceptor
Dispatchers.IO共享 IO 线程池(最多 64 个线程)
DispatchedContinuation把 Continuation 包装成 Runnable,投递到目标线程
ContinuationInterceptor协程上下文中的拦截器,在协程启动时负责包装 Continuation
StandaloneCoroutinelaunch 创建的协程对象,持有 CoroutineContext
CoroutineContext
    └── Dispatchers.IO(实现了 ContinuationInterceptor)
            └── 拦截 Continuation,包装成 DispatchedContinuation
                    └── 投递到 IO 线程池

launch 启动全流程

以下面这段代码为例:

// 主线程点击事件
lifecycleScope.launch(Dispatchers.IO) {
    log("thread = ${Thread.currentThread()} >>> delay: 开始")  // IO 线程
    delay(1000)
    log("thread = ${Thread.currentThread()} >>> delay: 结束")  // 仍在 IO 线程
}

分 5 步拆解

Step 1:launch 合并 CoroutineContext

// 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() 触发拦截器

// AbstractCoroutine.start()
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    └── block.startCoroutineCancellable(coroutine)
            └── createCoroutineUnintercepted(...)   // 创建原始 Continuation
                    .intercepted()                  // ← 关键!触发拦截器
                    .resumeCancellableWith(Result.success(Unit))

Step 3:intercepted() 包装成 DispatchedContinuation

// 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 线程池

// 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 线程取出任务,执行协程体

// IO 线程池的某个线程取出任务:
DispatchedContinuation.run()  // Runnable.run()
    └── continuation.resumeWith(result)        // 恢复 Continuation
            └── invokeSuspend(Unit)             // 进入协程状态机
                    └── 执行协程体(此时已在 IO 线程)
                            log("thread = IO线程")
                            delay(1000)  ...

关键源码逐层拆解

DispatchedContinuation —— 线程切换的载体

// 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 线程池实现

// 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

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 恢复时:

// 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<br/>2. 创建 StandaloneCoroutine<br/>3. 调用 block.startCoroutineCancellable()

    F->>F: continuation.intercepted()
    Note over F: Dispatchers.IO.interceptContinuation(cont)<br/>→ 包装成 DispatchedContinuation(IO, cont)

    F->>IO: dispatcher.dispatch(context, dispatchedContinuation)
    Note over IO: 把 DispatchedContinuation(Runnable)<br/>投入 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 线程<br/>log(">>> delay: 开始")

    IO->>T: delay(1000, continuation)<br/>scheduleResumeAfterDelay(1000ms, cont)
    Note over T: cont 绑定了 Dispatchers.IO<br/>保存 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<br/>Dispatcher = Dispatchers.IO<br/>→ 需要 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 线程<br/>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.DefaultDispatchers.IO
线程池共享的 CoroutineScheduler共享的 CoroutineScheduler(同一个!)
最大线程数CPU 核心数(通常 4~8)max(64, CPU核心数)
任务类型标记TASK_NON_BLOCKINGTASK_PROBABLY_BLOCKING
适用场景CPU 密集运算阻塞型 I/O(网络、文件、数据库)
dispatch() 实现投入同一调度器,限制并发数投入同一调度器,允许更多并发

两者底层用的是同一个 CoroutineSchedulerDispatchers.IO 只是放开了最大并发限制,允许更多线程同时阻塞等待 I/O。


三句话总结

步骤发生了什么
launch把协程体(Continuation)用 DispatchedContinuation 包装成 Runnablepost 到 IO 线程池
协程体执行IO 线程从队列取出 Runnable,调用 run()resumeWith()invokeSuspend(),协程体在 IO 线程运行
delay 后恢复定时器触发后,再次通过 Dispatchers.IO.dispatch() 投递到 IO 线程池,协程仍在 IO 线程继续