Le garanzie "succede prima" delle coroutine di Kotlin?


10

Le coroutine di Kotlin forniscono garanzie "prima"?

Ad esempio, esiste una garanzia "accade prima" tra la scrittura mutableVare la successiva lettura su (potenzialmente) altro thread in questo caso:

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

Modificare:

Forse un ulteriore esempio chiarirà meglio la domanda perché è più kotlin-ish (tranne che per la mutabilità). Questo codice è thread-safe:

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

private data class Data(var data: Int)

Si noti che durante l'esecuzione su JVM Kotlin utilizza lo stesso modello di memoria di Java.
Slaw,

1
@Slaw, lo so. Tuttavia, c'è molta magia in corso sotto il cofano. Pertanto, mi piacerebbe capire se ci sono delle garanzie prima che io riceva dalle coroutine o che tutto dipende da me.
Vasiliy,

Semmai, il tuo secondo esempio presenta uno scenario ancora più semplice: usa solo un oggetto creato all'interno withContext, mentre il primo esempio lo crea per primo, muta all'interno withContexte poi legge dopo withContext. Quindi il primo esempio esercita più funzioni di sicurezza del thread.
Marko Topolnik,

... ed entrambi gli esempi esercitano solo l'aspetto "ordine del programma" di accade prima, il più banale. Sto parlando a livello di coroutine qui, non alla JVM sottostante. Quindi, in sostanza, ti stai chiedendo se le coroutine di Kotlin sono così gravemente rotte che non forniscono nemmeno l'ordine del programma prima.
Marko Topolnik,

1
@MarkoTopolnik, correggimi se sbaglio, ma JLS garantisce solo "l'ordine del programma accade prima" per l'esecuzione sullo stesso thread. Ora, con le coroutine, anche se il codice sembra sequenziale, in pratica ci sono alcuni macchinari che lo scaricano su thread diversi. Capisco il tuo punto "questa è una garanzia così fondamentale che non perderei nemmeno il mio tempo a controllarla" (da un altro commento), ma ho fatto questa domanda per ottenere una risposta rigorosa. Sono abbastanza sicuro che gli esempi che ho scritto sono thread-safe, ma voglio capire perché.
Vasiliy,

Risposte:


6

Il codice che hai scritto ha tre accessi allo stato condiviso:

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

I tre accessi sono rigorosamente ordinati in sequenza, senza concorrenza tra loro, e puoi stare certo che l'infrastruttura di Kotlin si occupa di stabilire un limite prima del passaggio al IOpool di thread e di nuovo alla tua routine di chiamata.

Ecco un esempio equivalente che potrebbe forse sembrare più convincente:

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

Poiché delayè una funzione sospendibile e poiché stiamo utilizzando il Defaultdispatcher supportato da un pool di thread, le righe 1, 2 e 3 possono essere eseguite su un thread diverso. Pertanto la tua domanda sulle garanzie prima che si verifichi vale anche per questo esempio. D'altro canto, in questo caso (spero) completamente ovvio che il comportamento di questo codice è coerente con i principi dell'esecuzione sequenziale.


1
Grazie. In realtà è la parte dopo "stare tranquilli" che mi ha motivato a porre questa domanda. Ci sono collegamenti a documenti che potrei leggere? In alternativa, anche i collegamenti al codice sorgente in cui si verifica questo limite prima dell'inizio sarebbero di grande aiuto (join, sincronizzazione o qualsiasi altro metodo).
Vasiliy,

1
Questa è una garanzia così fondamentale che non perderei nemmeno il mio tempo a controllarlo. Sotto il cofano si riduce executorService.submit()e c'è un tipico meccanismo di attesa per il completamento dell'attività (completamento di una CompletableFutureo qualcosa di simile). Dal punto di vista dei coroutini di Kotlin qui non c'è concorrenza.
Marko Topolnik,

1
Puoi pensare alla tua domanda come analoga alla domanda "il sistema operativo garantisce un successo prima di sospendere un thread e poi riprenderlo su un altro core?" I thread sono per coroutine ciò che i core della CPU sono per thread.
Marko Topolnik,

1
Grazie per la tua spiegazione. Tuttavia, ho posto questa domanda per capire perché funziona. Vedo il tuo punto, ma, finora, non è la risposta rigorosa che sto cercando.
Vasiliy,

2
Beh ... in realtà non penso che questo thread abbia stabilito che il codice è sequenziale. Lo ha affermato, sicuramente. Anch'io sarei interessato a vedere il meccanismo che garantisce che l'esempio si comporti come previsto, senza influire sulle prestazioni.
G. Blake Meike,

3

Le coroutine a Kotlin prevedono che avvengano prima delle garanzie.

La regola è: all'interno di una routine, il codice prima di una chiamata di funzione di sospensione si verifica prima del codice dopo la chiamata di sospensione.

Dovresti pensare alle coroutine come se fossero fili regolari:

Anche se una coroutine in Kotlin può essere eseguita su più thread, è proprio come un thread dal punto di vista dello stato mutevole. Non possono essere simultanee due azioni nella stessa procedura.

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

Tornando all'esempio di codice. Catturare vars nei corpi funzione lambda non è la migliore idea, specialmente quando lambda è un coroutine. Il codice prima di un lambda non si verifica prima del codice all'interno.

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


La regola è questa, in realtà: il codice prima di una chiamata della funzione di sospensione accade, prima che il codice all'interno della funzione di sospensione, accade prima del codice dopo la chiamata di sospensione. Questo, a sua volta, può essere generalizzata a "fine programma del codice è anche il codice del accade-prima di ordine". Nota l'assenza di qualcosa di specifico per le funzioni sospendibili in quella dichiarazione.
Marko Topolnik,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.