首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >解释kotlin协同机制中死锁的原因

解释kotlin协同机制中死锁的原因
EN

Stack Overflow用户
提问于 2021-07-27 09:43:33
回答 1查看 1.2K关注 0票数 2

在试验kotlin协同实验时,我遇到了一种情况,即出现死锁,这是我所没有想到的。

我将代码简化为以下显示问题的最小代码示例:

代码语言:javascript
复制
@Test
    fun deadlockTest() {
        runBlocking {
            val job = launch {
                runBlocking {
                    println("await cancellation")
                    awaitCancellation()
                }
            }
            println("launched job")
            delay(100)
            println("waited a bit")
            job.cancelAndJoin()
            println("canceled and joined")
        }
        assertTrue(true)
    }

结果是

代码语言:javascript
复制
launched job
await cancellation
waited a bit

它从未超越过job.cancelAndJoin,就好像出现了一些死锁。

如果我将代码稍微更改为以下内容:

代码语言:javascript
复制
@Test
    fun fixedDeadlockTest() {
        runBlocking {
            val job = launch {
                withContext(Dispatchers.Default) { // <-- this is the only difference
                    println("awaiting cancellation")
                    awaitCancellation()
                }
            }
            println("launched job")
            delay(100)
            println("waited a bit")
            job.cancelAndJoin()
            println("canceled and joined")
        }
        assertTrue(true)
    }

一切正常,所有的行都被打印出来,测试完成。

问题是:为什么这段代码会导致死锁,将runBlocking放入另一个runBlocking的启动中是一种不好的做法吗?(也就是说,在您的代码中永远不要使用runBlocking,直到您真正从非协同范围启动协同线吗?)

我使用了以下版本:

多平台1.4.32

  • kotlinx-coroutines-core 1.5.0-native-mt

  • kotlin
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-07-27 10:00:32

Coroutines使用所谓的结构化并发来支持取消、异常处理等。它们被构造成一棵作业树,所以通常,当您创建新的coroutine时,它们就会成为当前协同线的子级。父母和子女之间有具体的责任,例如取消父母取消其所有子女。

然而,有一些方法可以启动不与当前协同线相连的协同线。例如,当您提供特定的CoroutineScope或使用runBlocking()时,就会发生这种情况。请注意,与launch()async()相反,runBlocking()不需要从协同线运行。它主要用于桥接非协同线和协同线代码,因此它从“根”开始创建协同线--它们与其他coroutine分离。

由于上述原因,您的示例中的cancelAndJoin()取消了在launch()中运行的协同线,但它不会取消在runBlocking()中运行的协同线。"awaitCancellation“协同线与”启动“协同线分离,因此它忽略了它的取消。

下面是我最初的答案。我对这一僵局的主要原因是错误的,但我所说的基本上仍然是正确的,它补充了上面的答案,所以我保留原样。我之所以错了,是因为我忘记了runBlocking()内部使用线程局部变量来存储它的事件循环。这意味着运行在另一个runBlocking()中的runBlocking()实际上共享相同的事件循环/调度程序,因此通过在awaitCancellation()挂起它就可以从delay()恢复。不过,我认为这样做是不可取的。

原始答案:

死锁的发生是因为外部runBlocking()启动了一个单线程协同调度器来在其中启动协同器,而内部runBlocking()阻塞了这个单一和唯一的线程。

您是对的,runBlocking()主要用于桥接非协同线和协同线代码.没有硬性规定在协同线内禁止使用runBlocking(),但一般情况下,我们应该避免阻塞内部协同线,而应该暂停。runBlocking()阻塞,因此它是不鼓励的,并可能导致像上面这样的后果。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68542554

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档