协作式调度:yield 与性能
Kotlin 协程不仅是异步的,更是协作式 (Cooperative) 的。这意味着如果不主动让出执行权,一个死循环协程就能彻底卡死所在的线程。
线程饥饿 (Starvation)
场景:在 Dispatchers.Default(通常只有 CPU 核心数个线程)中运行密集计算。
kotlin
launch(Dispatchers.Default) {
// 假设这个循环运行 10 秒
// 它会一直占用一个 CPU 核心线程不放
while (true) {
// 纯计算,没有挂起点
heavyCalc()
}
}如果有 8 个这样的协程同时运行在 8 核机器上,其他所有 Default 调度器的任务(包括 UI 差分计算)都会被饿死,得不到执行机会。
解决方案:yield()
yield() 是一个神奇的挂起函数。
- 检查取消: 如果当前 Job 已取消,抛出
CancellationException。 - 让出执行权: 它可以暂停当前协程,将其放入任务队列的末尾,让其他排队的任务先执行。
kotlin
launch(Dispatchers.Default) {
while (true) {
heavyCalc()
yield() // ✅ 让一让,让别人也能跑
}
}yield() vs ensureActive()
两者都常用于循环中以响应取消。
| 方法 | 功能 | 调度开销 | 适用场景 |
|---|---|---|---|
ensureActive() | 仅检查取消 | 极低 (无调度) | 仅为了能在取消时及时停止 |
yield() | 检查取消 + 让出 CPU | 有 (包含调度) | 防止长期霸占 CPU,平衡系统负载 |
性能提示
yield() 虽好,但不要滥用。过于频繁的 yield()(例如在极短的循环内)会导致频繁的线程上下文切换,反而降低总吞吐量。 建议:对于极高频循环,每 N 次迭代调用一次 yield()。
kotlin
var counter = 0
while (true) {
if (++counter % 1000 == 0) yield() // 每 1000 次让一次
compute()
}