Kotlin Coroutinen "passiert vorher" Garantien?

10

Bieten Kotlin-Coroutinen Garantien für "Vorheriges"?

mutableVarGibt es in diesem Fall beispielsweise eine "Vorher-passiert" -Garantie zwischen dem Schreiben in und dem anschließenden Lesen eines (möglicherweise) anderen Threads:

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

Bearbeiten:

Vielleicht klärt ein zusätzliches Beispiel die Frage besser, weil es mehr Kotlin-artig ist (mit Ausnahme der Veränderlichkeit). Ist dieser Code threadsicher:

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

private data class Data(var data: Int)
Vasiliy
quelle
Beachten Sie, dass Kotlin bei der Ausführung auf der JVM dasselbe Speichermodell wie Java verwendet.
Slaw
1
@Slaw, das weiß ich. Es gibt jedoch viel Magie unter der Haube. Daher würde ich gerne verstehen, ob es irgendwelche Garantien gibt, die ich von Coroutinen bekomme, oder ob alles bei mir liegt.
Vasiliy
Wenn überhaupt, stellt Ihr zweites Beispiel ein noch einfacheres Szenario dar: Es verwendet nur ein darin erstelltes Objekt withContext, während das erste Beispiel es zuerst erstellt, darin mutiert withContextund danach liest withContext. Im ersten Beispiel werden also mehr Thread-Sicherheitsfunktionen ausgeführt.
Marko Topolnik
... und beide Beispiele behandeln nur den Aspekt "Programmreihenfolge" von "Vorher", den trivialsten. Ich spreche hier auf der Ebene der Coroutinen, nicht auf der zugrunde liegenden JVM. Sie fragen sich also im Grunde, ob Kotlin-Coroutinen so stark beschädigt sind, dass sie nicht einmal die Programmreihenfolge bereitstellen, bevor dies geschieht.
Marko Topolnik
1
@MarkoTopolnik, korrigieren Sie mich, wenn ich falsch liege, aber JLS garantiert nur "Programmreihenfolge passiert vorher" für die Ausführung auf demselben Thread. Bei Coroutinen gibt es in der Praxis einige Maschinen, die den Code in verschiedene Threads auslagern, obwohl der Code sequentiell aussieht. Ich verstehe Ihren Punkt "Dies ist eine so grundlegende Garantie, dass ich nicht einmal meine Zeit damit verschwenden würde, sie zu überprüfen" (aus einem anderen Kommentar), aber ich habe diese Frage gestellt, um eine strenge Antwort zu erhalten. Ich bin mir ziemlich sicher, dass die Beispiele, die ich geschrieben habe, threadsicher sind, aber ich möchte verstehen, warum.
Vasiliy

Antworten:

6

Der von Ihnen geschriebene Code hat drei Zugriffe auf den freigegebenen Status:

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

Die drei Zugriffe sind streng nacheinander angeordnet, ohne dass eine Parallelität zwischen ihnen besteht, und Sie können sicher sein, dass die Infrastruktur von Kotlin dafür sorgt, dass bei der Übergabe an den Thread-Pool und zurück an Ihre aufrufende Coroutine eine Vorab- Kante entsteht IO.

Hier ist ein gleichwertiges Beispiel, das vielleicht überzeugender aussieht:

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

Da delayes sich um eine suspendierbare Funktion handelt und wir den DefaultDispatcher verwenden, der von einem Thread-Pool unterstützt wird, können die Zeilen 1, 2 und 3 jeweils in einem anderen Thread ausgeführt werden. Daher gilt Ihre Frage zu Vorabgarantien auch für dieses Beispiel. Andererseits ist es in diesem Fall (ich würde hoffen) völlig offensichtlich, dass das Verhalten dieses Codes mit den Prinzipien der sequentiellen Ausführung übereinstimmt.

Marko Topolnik
quelle
1
Vielen Dank. Es ist tatsächlich der Teil nach "seien Sie versichert", der mich motiviert hat, diese Frage zu stellen. Gibt es Links zu Dokumenten, die ich lesen könnte? Alternativ wären auch Links zu Quellcode, bei denen dies geschieht, bevor die Kante hergestellt wird, eine große Hilfe (Join, Synchronisation oder eine andere Methode).
Vasiliy
1
Dies ist eine so grundlegende Garantie, dass ich nicht einmal meine Zeit damit verschwenden würde, sie zu überprüfen. Unter der Haube executorService.submit()läuft es darauf hinaus und es gibt einen typischen Mechanismus, um auf den Abschluss der Aufgabe zu warten (Abschluss einer CompletableFutureoder ähnlicher Aufgaben ). Aus Sicht der Kotlin Coroutines gibt es hier überhaupt keine Parallelität.
Marko Topolnik
1
Sie können sich Ihre Frage als analog zu der Frage vorstellen: "Garantiert das Betriebssystem, dass dies passiert, bevor ein Thread angehalten und dann auf einem anderen Kern fortgesetzt wird?" Threads dienen zur Coroutine, CPU-Kerne zu Threads.
Marko Topolnik
1
Danke für Ihre Erklärung. Ich habe diese Frage jedoch gestellt, um zu verstehen, warum es funktioniert. Ich verstehe Ihren Standpunkt, aber bisher ist es nicht die strenge Antwort, nach der ich suche.
Vasiliy
2
Nun ... Ich glaube nicht, dass dieser Thread festgestellt hat, dass der Code sequentiell ist. Es hat es sicherlich behauptet. Ich wäre auch daran interessiert, den Mechanismus zu sehen, der garantiert, dass sich das Beispiel wie erwartet verhält, ohne die Leistung zu beeinträchtigen.
G. Blake Meike
3

Coroutinen in Kotlin bieten vor Garantien.

Die Regel lautet: Innerhalb einer Coroutine erfolgt der Code vor einem Suspend-Funktionsaufruf vor dem Code nach dem Suspend-Aufruf.

Sie sollten sich Coroutinen so vorstellen, als wären sie normale Threads:

Obwohl eine Coroutine in Kotlin auf mehreren Threads ausgeführt werden kann, ist sie vom Standpunkt eines veränderlichen Zustands aus wie ein Thread. Es können keine zwei Aktionen in derselben Coroutine gleichzeitig ausgeführt werden.

Quelle: https://proandroiddev.com/what-is-concurrent-access-to-mutable-state-f386e5cb8292

Zurück zum Codebeispiel. Das Erfassen von Vars in Lambda-Funktionskörpern ist nicht die beste Idee, insbesondere wenn Lambda eine Coroutine ist. Der Code vor einem Lambda kommt nicht vor dem Code vor.

Siehe https://youtrack.jetbrains.com/issue/KT-15514

Sergei Voitovich
quelle
Die Regel lautet tatsächlich: Der Code vor einem Suspend-Funktionsaufruf erfolgt - bevor der Code innerhalb der Suspend-Funktion erfolgt - vor dem Code nach dem Suspend-Aufruf. Dies wiederum kann zu verallgemeinern „das Programm , um den Code ist auch der Code des geschieht zuvor Ordnung“. Beachten Sie das Fehlen von spezifischen Informationen zu suspendierbaren Funktionen in dieser Anweisung.
Marko Topolnik