Kotlin协程“事前发生”的保证?


10

Kotlin协程是否提供任何“事前保证”?

例如,mutableVar在这种情况下,对(可能)其他线程的写入和后续读取之间是否存在“先于后继”保证:

suspend fun doSomething() {
    var mutableVar = 0
    withContext(Dispatchers.IO) {
        mutableVar = 1
    }
    System.out.println("value: $mutableVar")
}

编辑:

也许还有其他例子可以更好地阐明这个问题,因为它更具Kotlin风格(可变性除外)。这段代码是线程安全的吗:

suspend fun doSomething() {
    var data = withContext(Dispatchers.IO) {
        Data(1)
    }
    System.out.println("value: ${data.data}")
}

private data class Data(var data: Int)

请注意,在JVM上运行时,Kotlin使用与Java相同的内存模型。
Slaw

1
@Slaw,我知道。但是,引擎盖下发生了很多魔术。因此,我想了解从协程获得的保证之前是否发生任何事情,或者这一切都在我身上。
Vasiliy

如果有的话,您的第二个示例提出了一个更简单的方案:它仅使用在中创建的对象withContext,而第一个示例首先创建它,在其中进行突变withContext,然后读取withContext。因此,第一个示例具有更多的线程安全功能。
Marko Topolnik,

...和这两个示例都仅体现了之前发生的“程序顺序”方面,这是最琐碎的。我在这里说的是协程级别,而不是底层的JVM。因此,基本上,您是在询问Kotlin协程是否严重损坏,以至于它们甚至都无法提供程序顺序。
Marko Topolnik,

1
@MarkoTopolnik,如果我错了,请纠正我,但JLS仅保证“程序顺序发生在-之前”才能在同一线程上执行。现在,有了协程,即使代码看起来是顺序的,实际上,也有一些机制将其卸载到不同的线程。我理解您的观点:“这是一项基本保证,我什至不会浪费时间检查它”(来自另一条评论),但是我问这个问题以获得严格的答案。我很确定我编写的示例是线程安全的,但我想了解原因。
Vasiliy

Answers:


6

您编写的代码具有对共享状态的三种访问方式:

var mutableVar = 0                        // access 1, init
withContext(Dispatchers.IO) {
    mutableVar = 1                        // access 2, write
}
System.out.println("value: $mutableVar")  // access 3, read

严格按顺序对这三个访问进行排序,它们之间没有并发性,并且您可以放心,Kotlin的基础结构会在切换到线程池并返回到调用协程时负责边缘发生事件IO

这是一个等效的示例,可能看起来更令人信服:

launch(Dispatchers.Default) {
    var mutableVar = 0             // 1
    delay(1)
    mutableVar = 1                 // 2
    delay(1)
    println("value: $mutableVar")  // 3
}

因为delay是可挂起的函数,并且由于我们使用的Default是由线程池支持的调度程序,所以第1、2和3行可能分别在不同的线程上执行。因此,您关于事前发生担保的问题同样适用于此示例。另一方面,在这种情况下(我希望)是完全显而易见的,该代码的行为与顺序执行的原理一致。


1
谢谢。实际上是“放心”之后激发我提出这个问题的部分。有没有我可以阅读的文档链接?或者,链接到在边缘建立之前发生的源代码也将有很大的帮助(联接,同步或任何其他方法)。
Vasiliy

1
这是基本保证,我什至不会浪费时间检查它。在幕后,它归结为executorService.submit()一种典型的机制,等待任务完成(完成a CompletableFuture或类似任务)。从Kotlin协程的观点来看,这里根本没有并发性。
Marko Topolnik

1
您可以认为您的问题类似于询问“操作系统是否保证在挂起线程然后将其恢复到另一个内核之前发生?”。线程用来确定CPU核心是线程。
Marko Topolnik,

1
感谢您的解释。但是,我问这个问题以了解它为什么起作用。我明白您的意思,但是到目前为止,这并不是我要的严格答案。
Vasiliy

2
好吧……我实际上不认为该线程确定了代码是顺序的。它肯定地断言了它。我也很想看到一种机制,可以确保示例表现出预期的效果,而不会影响性能。
G. Blake Meike

3

Kotlin中的协程确实会在保证发生之前提供保证。

规则是:协同程序,代码之前的暂停函数调用之前发生的代码暂停通话。

您应该考虑协程,就像它们是常规线程一样:

即使Kotlin中的协程可以在多个线程上执行,但从可变状态的角度来看,它就像一个线程。同一协程中没有两个动作可以同时进行。

资料来源:https : //proandroiddev.com/what-is-concurrent-access-to-mutable-state-f386e5cb8292

回到代码示例。在lambda函数体中捕获var不是最好的主意,尤其是当lambda是协程时。Lambda之前的代码不会在内部代码之前发生。

参见https://youtrack.jetbrains.com/issue/KT-15514


规则实际上是这样的:挂起函数调用之前的代码发生在–挂起函数内部的代码之前– 发生在–挂起调用之后的代码之前。反过来,这可以概括为“代码的程序顺序也是代码的先发生顺序”。请注意,该语句中没有任何特定于可挂起函数的内容。
Marko Topolnik,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.