协程原理

Kotlin协程的本质是通过状态机管理挂起点,由编译器进行CPS变换实现的轻量级并发抽象。其核心原理和状态推进机制如下:

核心原理

1. 挂起函数
  • 用suspend修饰的函数
  • 编译器会将其编译为状态机代码(而非阻塞线程),支持在任意位置挂起/恢复
2. 续体
  • 类似回调的接口Continuation<T>,其关键方法是resumeWith(result)
  • 协程的每一步执行都依附于一个续体对象,存储当前执行状态和上下文
3. 状态机转换
  • 编译器将挂起函数拆解成一个状态机(通过label标记状态)
  • 每个挂起点对应一个状态迁移

状态推进流程

以下代码展示状态机的运作:

suspend fun fetchData(): String {
  val data1 = fetchPart1()  //挂起点1
  val data2 = fetchPart2()  //挂起点2
  return data1 + data2
}

编译器转换后(伪代码)

class FetchDataStateMachine(
	val completion: Continuation<String>,
  var label: Int = 0
) : Continuation<Unit> {
  var data1: String? = null
  var data2: String? = null
  
  override fun resumeWith(result: Result<Any?>) {
    when(label) {
      0 -> {
        label = 1
        fetchPart1(this)
      }
      1 -> {
        data1 = result.getOrThrow() as String
        label = 2
        fetchPart2(this)
      }
      2 -> {
        data2 = result.getOrThrow() as String
        completion.resumeWith(data1 + data2)	//返回最终结果
      }
    }
  }
}

关键机制

1. 挂起不阻塞线程:
  • 协程挂起时,底层线程立即释放(例如返回到线程池),避免资源浪费
  • 异步操作完成后,任务被派发到合适的线程继续执行(通过Dispatcher
2.续体传递风格
  • 挂起函数被编译为接受额外Continuation参数的函数
  • 例如suspend fun foo()fun foo(continuation: Continuation)
3. 协程上下文(CoroutineContext)
  • 通过CoroutineContext传递调度器、异常处理器等。
  • 状态机中通过Continuation.context获取当前上下文
4. 结构化并发
  • 协程树通过父-子关系管理生命周期
  • 父协程取消时,自动取消所有子协程

状态推进

FetchDataStateMachineresumeWith中并没有循环,label的状态是如何推进的呢?实际上状态推进是通过递归链式调用与间接跳转实现的。

1. 单次触发模型
  • 每次resumeWith被调用时只处理当前状态
  • 通过更新label值标记下一步状态
  • 不立即处理后续状态,而是等待下一次恢复
label = 2  //只标记下一步状态,不立即执行
fetchPart2(this)  //触发异步操作(挂起),this就是FetchDataStateMachine,其是Continuation,可通过this调用resumeWith
2. 链式递归唤醒
  • 每个异步操作完成时,都会重新调用resumeWith
  • 每调用一次,就会处理当前状态并设置下一次状态
resumeWith(结果) → 处理当前状态
      ↑               ↓
  异步完成          设置下一状态
      ↑             
  恢复执行        
3. 状态变量持久化
  • 状态机对象在挂起期间持续存在(堆内存)
  • 成员变量(data1, label)保存中间状态
  • 每次恢复时从正确状态继续执行
4. 编译器优化技巧
  • 尾递归优化:编译器会将状态处理转为循环
  • 状态折叠 :合并可优化状态减少跳转次数
  • 内联状态:简单状态机转为switch跳转表

对挂起的理解

协程挂机:在挂起点暂停当前的同步代码,转而去执行消息队列的runnable;这样就是我对挂起的理解,也就是让出线程

使用 Hugo 构建
主题 StackJimmy 设计