第 9 章 · 协程基础(异步并发的现代答案)

写同步代码,跑出异步效果。100 万协程也不挂。

⏱ 串行 vs 并发:协程让你免费"赚到"几百毫秒

需求:拿用户、订单、支付三个数据。每个 IO 大约 500ms。

方案 A:串行(默认 suspend 调用)

val u = fetchUser()      // 500ms
val o = fetchOrders()    // 500ms
val p = fetchPayment()   // 500ms
// 总: 1500ms
协程
fetchUser 500ms
fetchOrders 500ms
fetchPayment 500ms

方案 B:并发 async

val u = async { fetchUser() }       // 同时启动
val o = async { fetchOrders() }
val p = async { fetchPayment() }
val all = listOf(u, o, p).awaitAll()
// 总: max(500ms) = 500ms
协程 1
fetchUser 500ms
协程 2
fetchOrders 500ms
协程 3
fetchPayment 500ms
🎯 关键:3 个协程都在 IO 等待,只用 4 个 OS 线程的线程池就能"假装并发",实际只用了 1 个线程的算力。 这就是为什么协程能轻松起 100 万个。

🚀 协程 vs 线程:起 1 万个并发任务

🧵 Java Thread 模型

repeat(10_000) {
    Thread {
        Thread.sleep(1000)
        // 模拟 IO
    }.start()
}
线程数10,000
内存占用~10 GB
JVM OOM大概率挂
总耗时> 1s

⚡ Kotlin 协程模型

runBlocking {
    repeat(10_000) {
        launch {
            delay(1000)
            // 模拟 IO
        }
    }
}
线程数~8(线程池)
内存占用~50 MB
JVM OOM不会
总耗时~1s

为什么差距这么大?

  • Thread.sleep:占用一整个 OS 线程不放,1000 个并发要 1000 个线程
  • delay:协程让出当前线程,事件来了再恢复。实际跑只用线程池里的几个 OS 线程
  • OS 线程切换涉及内核态调度(昂贵),协程切换是用户态(便宜)

📊 真实数据(macOS M1, JDK 17)

并发任务数Thread协程
100200ms110ms
10,000OOM1.2s
1,000,000不可能3.5s

🔬 suspend 函数的"魔法":CPS 编译

编译器把 suspend 函数变成"状态机",每次挂起都保存当前状态。

📝 你写的代码

suspend fun loadUser(): User {
    val data = fetchData()       // suspend 调用 1
    val parsed = parseJson(data) // suspend 调用 2
    return User(parsed)
}

🤖 编译器生成的状态机(伪代码)

State 0:开始

调用 fetchData,挂起,保存 state=1

State 1:fetchData 结果到了

恢复,data = ...,调用 parseJson,挂起,保存 state=2

State 2:parseJson 结果到了

恢复,return User(parsed)

💡 关键点

  • 每次挂起,线程是被释放的,去给别的协程用
  • 异步操作完成后,回调会调用 continuation.resume(result)
  • resume 会让协程恢复,从下一个状态继续跑
  • 这就是为什么代码看起来同步,实际是异步的"魔法"

🌊 Flow:异步数据流(弹珠图)

Flow 像 RxJava 的 Observable + suspend,每秒发出一个值。

flow { for (i in 1..6) { delay(800); emit(i) } }
    .filter { it % 2 == 0 }
    .map { it * 10 }
    .collect { println(it) }

① 源 Flow(每 800ms 发出一个)

② filter { it % 2 == 0 }(只留偶数)

③ map { it * 10 }(每个 ×10)

④ collect { println(it) }(最终输出)

🆚 Flow / StateFlow / SharedFlow 对比

类型冷热典型场景
Flow一次性数据流(DB 查询、网络请求)
StateFlow热(保留当前值)UI 状态(loading/success/error)
SharedFlow事件广播(toast、navigate)