You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程runBlocking无限循环问题:移除首个yield()为何能修复?

Kotlin协程无限循环问题:移除首个yield()为何能修复?

我在GitHub的Kotlin协程issue里找到这段代码示例,它是个协程无限循环场景(不是死锁,因为有协程一直在跑)。调试时发现while (job.isActive) yield()一直在重复调度自身,根本没按预期调度job协程。搞不懂为啥移除第一个yield()就能解决问题,求解释代码失效的原因,以及移除首个yield()能修复的缘由。

@Test
fun testDeadlock() = runBlocking {
    val job = launch {
        runBlocking {
            println("1")
        }
        println("2")
    }

    yield() // 注释掉这行测试就能通过
    runBlocking {
        println("3")
        while (job.isActive) yield()
    }
}

代码失效的原因

  • 先搞清楚runBlocking的核心特性:它会阻塞当前线程,直到内部协程全部执行完,而且会独占当前线程的事件循环。
  • 当第一个yield()执行时,它让根协程(外层runBlocking的协程)暂停,把线程让给job协程。job启动后立刻进入内部的runBlocking,这个内部runBlocking会霸占线程,执行完println("1")才释放线程。
  • 但第一个yield()执行完回到根协程后,马上进入第二个runBlocking。这个新的runBlocking又立刻抢占线程的事件循环,此时job刚执行完内部runBlocking,正要执行println("2"),却被第二个runBlocking抢了调度权。
  • 第二个runBlocking里的while (job.isActive) yield(),每次yield()都是在自身内部的事件循环里调度,根本不会把线程让给外部的job协程。因为runBlocking的事件循环只处理自己内部的协程任务,所以这个循环会无限跑下去,job永远没机会执行println("2"),也就永远不会结束,形成无限循环。

移除首个yield()能修复的原因

  • 去掉第一个yield()后,根协程会直接进入第二个runBlocking。此时job协程还没被调度过(launch是异步启动,根协程不暂停就会先走到第二个runBlocking)。
  • 第二个runBlocking启动后,内部的yield()会触发调度,这时候事件循环会发现等待执行的job协程,于是调度job运行。job内部的runBlocking执行println("1")后释放线程,接着job执行println("2"),之后job结束,job.isActive变为false,循环终止,测试顺利通过。

内容的提问来源于stack exchange,提问作者Barni Bittner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 08:55:17