Kotlin 协程底层原理(Continuation)详解

Kotlin 协程底层原理(Continuation)详解

很多人会用协程:

kotlin 复制代码
launch { }
async { }
withContext { }

但真正理解协程底层后,你会发现:

Kotlin 协程本质上并不是"魔法异步"。

它本质是:

Continuation(续体) + 状态机

这才是协程真正核心。


一、先理解:为什么需要 Continuation?

先看普通函数。

普通函数执行过程

kotlin 复制代码
fun test() {
    println("1")
    println("2")
    println("3")
}

执行流程:

复制代码
1 -> 2 -> 3

函数执行完:

  • 栈帧销毁
  • 无法恢复

这叫:一次性执行。

但协程不同。


二、协程为什么能"暂停后恢复"?

kotlin 复制代码
suspend fun load() {
    println("开始")
    delay(3000)
    println("结束")
}

问题来了:

delay 后,协程暂停了。3秒后,它为什么还能继续从 println("结束") 开始执行?

普通函数做不到。

所以 Kotlin 编译器做了一件事:

把 suspend 函数改造成**"状态机"**

而 Continuation,就是保存状态的核心对象。


三、Continuation 是什么?

官方定义:

"协程执行到哪了"的记录器。

或者更简单:

Continuation = 协程存档点

它会记录:

  • 当前执行位置
  • 局部变量
  • 下一步执行逻辑
  • 协程上下文

四、最简单理解

你玩游戏:

  • 打到一半存档
  • 退出游戏
  • 下次继续

Continuation,就是这个"存档"。


五、suspend 本质是什么?

先看:

kotlin 复制代码
suspend fun request(): String

很多人以为 suspend 是关键字魔法。

其实编译后 ,suspend 函数会变成:

kotlin 复制代码
fun request(
    continuation: Continuation<String>
): Any

重点来了:

所有 suspend 函数,都会多一个 Continuation 参数

这是协程底层最大核心。


六、编译器偷偷干了什么?

你写:

kotlin 复制代码
suspend fun test() {
    delay(1000)
    println("完成")
}

编译器会生成类似:

kotlin 复制代码
class TestContinuation : Continuation<Unit> {
    var label = 0

    override fun resumeWith(result: Result<Unit>) {
        when(label) {
            0 -> {
                label = 1
                delay(1000, this)
            }
            1 -> {
                println("完成")
            }
        }
    }
}

这就是:

状态机(State Machine)


七、label 是什么?

kotlin 复制代码
var label = 0

它表示:当前协程执行到了哪个阶段

示例

kotlin 复制代码
suspend fun demo() {
    println("A")
    delay(1000)
    println("B")
    delay(1000)
    println("C")
}

编译后类似:

label 位置
0 开始
1 第一个delay后
2 第二个delay后

所以:

协程恢复时,只需要看 label,就知道该从哪里继续。


八、delay 为什么不会阻塞线程?

这是经典面试题。

Thread.sleep

kotlin 复制代码
Thread.sleep(3000)

线程:彻底卡死

delay

kotlin 复制代码
delay(3000)

发生了什么?

第一步:协程保存现场

  • Continuation 存档
  • 包括:label、局部变量、执行位置

第二步:当前线程被释放

  • 线程去执行别的任务

第三步:时间到了

  • 恢复 Continuation:continuation.resume()
  • 协程继续执行

所以:delay 挂起的是协程,不是线程。


九、resume 是什么?

Continuation 最核心方法:

kotlin 复制代码
resumeWith()

作用: 恢复协程执行

举例:

  • 协程暂停在 delay
  • 时间到 → resume()
  • 协程继续

十、真正的协程恢复流程

代码

kotlin 复制代码
launch {
    delay(1000)
    println("Hello")
}

底层流程

复制代码
1. launch 创建协程
   → 生成:Continuation对象

2. 执行到 delay
   → 发现:需要挂起

3. 保存状态
   → 保存:label、局部变量、上下文

4. 当前线程释放
   → 线程不阻塞

5. 1秒后
   → 定时器触发:continuation.resume()

6. 状态机恢复
   → 从:label = 1 继续执行

十一、协程为什么这么轻量?

线程 协程
OS内核调度 用户态对象
成本高 只是一堆 Continuation + 状态机对象

所以:

一个线程可以跑几十万个协程


十二、挂起点(Suspension Point)

只有 suspend 函数才能挂起。

例如:

  • delay()
  • withContext()
  • await()

这些都叫:挂起点


十三、为什么 suspend 只能在协程里调用?

因为:

suspend 函数需要 Continuation

普通函数没有。

所以:

kotlin 复制代码
fun main() {
    delay(1000) // 会报错
}

十四、withContext 为什么能切线程?

kotlin 复制代码
withContext(Dispatchers.IO)

本质:

  • 修改 Continuation 的 Dispatcher
  • 恢复协程时,调度器决定在哪个线程继续执行

十五、CoroutineContext

协程上下文,内部保存:

  • Dispatcher
  • Job
  • CoroutineName
  • ExceptionHandler

本质: 就是 Map 结构


十六、Job 底层原理

每个协程都有 Job,Job 维护:

  • 父子协程关系
  • cancel() 本质:修改协程状态,然后挂起点检测取消

十七、为什么协程取消不是立刻停止?

因为:协程是协作式取消,不是线程强杀。

例如:

kotlin 复制代码
while(true) { } // 不会停,因为没有检查取消状态

正确:

kotlin 复制代码
while(isActive) { }

十八、suspend 不等于异步

这是最容易误解的。

错误理解 正确理解
suspend = 开新线程 suspend 只是支持挂起与恢复,是否异步看你是否 launch / async

十九、真正的协程核心结构

协程底层可以简化成:

复制代码
Coroutine
    ↓
Continuation
    ↓
StateMachine
    ↓
Dispatcher

二十、协程 vs 回调

回调地狱

kotlin 复制代码
request {
    request2 {
        request3 {
            // ...
        }
    }
}

协程

kotlin 复制代码
val a = request()
val b = request2()
val c = request3()

为什么能这样? 因为 Continuation 替你保存了执行现场。


二十一、Retrofit 为什么支持 suspend?

Retrofit 遇到 suspend fun getUser():

本质: 它拿到了 Continuation

网络返回后,调用 continuation.resume(data),协程恢复。


二十二、最重要的一张图

普通函数

复制代码
执行 -> 结束 -> 消失

协程

复制代码
执行
    ↓
挂起
    ↓
保存Continuation
    ↓
线程释放
    ↓
恢复resume
    ↓
继续执行

二十三、协程源码里最重要的类

真正核心:

  1. Continuation(基类)
  2. ContinuationImpl
  3. BaseContinuationImpl
  4. DispatchedContinuation

二十四、源码中最经典的方法

kotlin 复制代码
resumeWith()

协程恢复入口。


二十五、面试高频题(非常重要)

1. suspend 本质是什么?

本质:编译器 CPS 转换

即 Continuation Passing Style

编译器自动添加 Continuation 参数。

2. 协程为什么不卡线程?

因为:挂起的是协程,不是线程

3. 协程为什么轻量?

因为:不走系统线程调度,本质只是对象和状态机

4. delay 为什么能恢复?

因为:continuation.resume()

5. 协程如何记住执行位置?

通过:label 状态机


二十六、真正理解协程的一句话

Kotlin 协程本质:

编译器把 suspend 函数转换成状态机

然后 Continuation 保存协程执行现场

最终实现:可暂停、可恢复、非阻塞

相关推荐
aqiu1111114 小时前
【C++算法打怪专栏】8. 字符串转换整数 (atoi)
开发语言·c++·算法
爱吃香菜的初学者4 小时前
十四.Linux——消息队列
linux·运维·开发语言
迅猛龙办公室5 小时前
python实现倒序输出数字
开发语言·python
夏幻灵11 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码11 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
墨天梦12 小时前
B19_SunnyWeather到阅读客户端
android·kotlin
bkspiderx12 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
代码什么用12 小时前
Spring基础使用
java·后端·spring
程序员老陆12 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光13 小时前
C语言入门,深入理解指针(3)
c语言·开发语言