Skip to content

协作式调度:yield 与性能

Kotlin 协程不仅是异步的,更是协作式 (Cooperative) 的。这意味着如果不主动让出执行权,一个死循环协程就能彻底卡死所在的线程。

线程饥饿 (Starvation)

场景:在 Dispatchers.Default(通常只有 CPU 核心数个线程)中运行密集计算。

kotlin
launch(Dispatchers.Default) {
    // 假设这个循环运行 10 秒
    // 它会一直占用一个 CPU 核心线程不放
    while (true) {
        // 纯计算,没有挂起点
        heavyCalc() 
    }
}

如果有 8 个这样的协程同时运行在 8 核机器上,其他所有 Default 调度器的任务(包括 UI 差分计算)都会被饿死,得不到执行机会。

解决方案:yield()

yield() 是一个神奇的挂起函数。

  1. 检查取消: 如果当前 Job 已取消,抛出 CancellationException
  2. 让出执行权: 它可以暂停当前协程,将其放入任务队列的末尾,让其他排队的任务先执行。
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()
}