为何这两个Kotlin协程函数行为不同?一个正常一个发送阻塞
两个Kotlin协程函数运行差异的原因
先看给定的代码:
主函数
fun main() = runBlocking { val channel = Channel<Int>() foo(channel) for (y in channel) println(y) println("Done!") }
正常运行的函数
fun CoroutineScope.foo(channel: Channel<Int>) { launch { for (x in 1..5) channel.send(x * x) channel.close() } }
阻塞卡死的函数
suspend fun foo(channel: Channel<Int>) = coroutineScope { launch { for (x in 1..5) channel.send(x * x) channel.close() } }
核心差异分析
首先要明确:Kotlin的Channel默认是RENDEZVOUS类型——没有缓冲区,send操作会挂起,必须等有接收方调用receive(或通过for循环迭代接收)才能继续执行。
第一个foo函数(正常运行)
- 它是
CoroutineScope的扩展函数,调用时直接在当前作用域(也就是runBlocking的协程作用域)下启动一个子协程。 - 调用
foo(channel)后,函数立即返回,不会阻塞runBlocking里的主协程。接下来主协程马上进入for (y in channel)开始接收数据,此时子协程的send操作能找到接收方,顺利发送所有数据,最后关闭Channel,for循环结束后打印Done!。
第二个foo函数(阻塞卡死)
- 它是挂起函数,内部用
coroutineScope创建了一个子作用域。coroutineScope的关键特性是:会挂起当前协程,直到内部所有子协程全部执行完毕才恢复。 - 当主协程调用
foo(channel)时,会被挂起,等待coroutineScope完成。但foo里的子协程执行channel.send时,主协程还没进入接收环节(因为foo还没返回),没有接收方,send会挂起这个子协程。 - 这就形成了死锁:
coroutineScope等子协程完成,子协程等接收方,而接收方(主协程的for循环)等foo返回才能执行,三方互相等待,程序彻底卡住。
总结
两个函数的本质区别在于协程作用域的生命周期管理:
- 第一个foo在外部作用域启动独立子协程,不阻塞主流程,send和receive能同步配合。
- 第二个foo用
coroutineScope强制等待内部子协程完成,但子协程的send依赖主流程的接收,最终导致死锁。
内容的提问来源于stack exchange,提问作者igr
相关产品推荐
相关产品推荐

