¿Garantías de Kotlin coroutines "pasa antes"?

10

¿Las corutinas de Kotlin ofrecen garantías de "sucede antes"?

Por ejemplo, ¿existe una garantía de "sucede antes" entre la escritura mutableVary la posterior lectura en (potencialmente) otro hilo en este caso:

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

Editar:

Tal vez un ejemplo adicional aclare mejor la pregunta porque es más Kotlin-ish (excepto por la mutabilidad). ¿Es este código seguro para subprocesos?

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

private data class Data(var data: Int)
Vasiliy
fuente
Tenga en cuenta que cuando se ejecuta en JVM, Kotlin utiliza el mismo modelo de memoria que Java.
Slaw
1
@ Law, lo sé. Sin embargo, hay mucha magia debajo del capó. Por lo tanto, me gustaría entender si hay garantías de que ocurra antes de las corutinas, o si todo depende de mí.
Vasiliy
En todo caso, su segundo ejemplo presenta un escenario aún más simple: solo usa un objeto creado dentro withContext, mientras que el primer ejemplo lo crea primero, muta dentro withContexty luego lee withContext. Entonces, el primer ejemplo ejerce más características de seguridad de hilo.
Marko Topolnik el
... y ambos ejemplos ejercitan solo el aspecto de "orden de programa" de suceder antes, el más trivial. Estoy hablando en el nivel de las rutinas aquí, no en la JVM subyacente. Entonces, básicamente, se pregunta si las corutinas de Kotlin están tan rotas que ni siquiera proporcionan el orden del programa antes.
Marko Topolnik el
1
@MarkoTopolnik, corrígeme si me equivoco, pero JLS solo garantiza que "el orden del programa ocurra antes" para la ejecución en el mismo hilo. Ahora, con las rutinas, a pesar de que el código parece secuencial, en la práctica hay algo de maquinaria que lo descarga a diferentes hilos. Entiendo su punto "esta es una garantía tan básica que ni siquiera perdería mi tiempo revisándola" (de otro comentario), pero hice esta pregunta para obtener una respuesta rigurosa. Estoy bastante seguro de que los ejemplos que escribí son seguros para subprocesos, pero quiero entender por qué.
Vasiliy

Respuestas:

6

El código que escribió tiene tres accesos al estado compartido:

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

Los tres accesos están estrictamente ordenados secuencialmente, sin concurrencia entre ellos, y puede estar seguro de que la infraestructura de Kotlin se encarga de establecer un límite antes deIO pasar al grupo de subprocesos y volver a su rutina de llamadas.

Aquí hay un ejemplo equivalente que quizás parezca más convincente:

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

Dado que delayes una función suspendible, y dado que estamos utilizando el Defaultdespachador respaldado por un grupo de subprocesos, las líneas 1, 2 y 3 pueden ejecutarse en un subproceso diferente. Por lo tanto, su pregunta sobre suceder antes garantías de aplica igualmente a este ejemplo. Por otro lado, en este caso es (espero) completamente obvio que el comportamiento de este código es consistente con los principios de ejecución secuencial.

Marko Topolnik
fuente
1
Gracias. En realidad, fue la parte después de "estar tranquilo" lo que me motivó a hacer esta pregunta. ¿Hay algún enlace a documentos que pueda leer? Alternativamente, los enlaces al código fuente donde sucede esto antes de que se establezca edge también serían de gran ayuda (unión, sincronización o cualquier otro método).
Vasiliy
1
Esta es una garantía tan básica que ni siquiera perdería mi tiempo revisándola. Debajo del capó se reduce a executorService.submit()y hay algún mecanismo típico de espera a la finalización de la tarea (completar una CompletableFutureo algo similar). Desde el punto de vista de las corutinas de Kotlin no hay concurrencia en absoluto aquí.
Marko Topolnik el
1
Puede pensar que su pregunta es análoga a preguntar "¿garantiza el sistema operativo que suceda antes cuando suspende un hilo y luego lo reanuda en otro núcleo?" Los hilos son para las rutinas lo que los núcleos de la CPU son para los hilos.
Marko Topolnik el
1
Gracias por tu explicación. Sin embargo, hice esta pregunta para entender por qué funciona. Entiendo su punto, pero, hasta ahora, no es la respuesta rigurosa que estoy buscando.
Vasiliy
2
Bueno ... en realidad no creo que este hilo haya establecido que el código es secuencial. Lo ha afirmado, seguramente. A mí también me interesaría ver el mecanismo que garantiza que el ejemplo se comporte como se espera, sin afectar el rendimiento.
G. Blake Meike
3

Las corutinas en Kotlin ofrecen antes que las garantías.

La regla es: dentro de una rutina, el código antes de una llamada de función de suspensión ocurre antes del código después de la llamada de suspensión.

Debes pensar en las corutinas como si fueran hilos regulares:

Aunque una corutina en Kotlin puede ejecutarse en múltiples hilos, es como un hilo desde un punto de vista de estado mutable. No hay dos acciones en la misma rutina que puedan ser concurrentes.

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

Volviendo al ejemplo de código. La captura de vars en cuerpos de funciones lambda no es la mejor idea, especialmente cuando lambda es una rutina. El código anterior a un lambda no ocurre antes del código interno.

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

Sergei Voitovich
fuente
La regla es esta, en realidad: el código antes de una llamada a la función de suspensión ocurre antes de que el código dentro de la función de suspensión ocurra antes del código después de la llamada de suspensión. Esto, a su vez, se puede generalizar a "el orden del programa del código es también el orden del código que ocurre antes del código ". Tenga en cuenta la ausencia de algo específico para funciones suspendibles en esa declaración.
Marko Topolnik el