As corotinas Kotlin “acontecem antes” garantem?

10

As corotinas da Kotlin fornecem alguma garantia "acontece antes"?

Por exemplo, existe uma garantia "acontece antes" entre a gravação mutableVare a leitura subsequente (potencialmente) em outro encadeamento neste caso:

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

Editar:

Talvez um exemplo adicional esclareça melhor a questão, pois é mais Kotlin-ish (exceto pela mutabilidade). Esse código é seguro para threads:

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

private data class Data(var data: Int)
Vasiliy
fonte
Observe que ao executar na JVM Kotlin usa o mesmo modelo de memória que Java.
Slaw
11
@ Slaw, eu sei disso. No entanto, há muita magia acontecendo sob o capô. Portanto, eu gostaria de entender se há alguma garantia prévia que recebo das corotinas, ou é tudo sobre mim.
Vasiliy
De qualquer forma, seu segundo exemplo apresenta um cenário ainda mais simples: ele usa apenas um objeto criado dentro withContext, enquanto o primeiro exemplo o cria primeiro, muda dentro withContexte depois lê withContext. Portanto, o 1º exemplo exercita mais recursos de segurança de threads.
Marko Topolnik 23/10/19
... e os dois exemplos exercitam apenas o aspecto "ordem do programa" de acontece antes, o mais trivial. Eu estou falando no nível de corotinas aqui, não na JVM subjacente. Então, basicamente, você está perguntando se as corotinas Kotlin estão ou não tão severamente quebradas que nem sequer fornecem a ordem do programa antes.
Marko Topolnik 23/10/19
11
@ MarkoTopolnik, corrija-me se estiver errado, mas o JLS apenas garante "a ordem do programa acontece antes" para execução no mesmo encadeamento. Agora, com as corotinas, mesmo que o código pareça sequencial, na prática, existem algumas máquinas que o descarregam em diferentes segmentos. Entendo o seu argumento "essa é uma garantia tão básica que eu nem perderia meu tempo conferindo" (de outro comentário), mas fiz essa pergunta para obter uma resposta rigorosa. Tenho certeza de que os exemplos que escrevi são seguros para threads, mas quero entender o porquê.
Vasiliy

Respostas:

6

O código que você escreveu possui três acessos ao estado compartilhado:

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

Os três acessos são estritamente ordenados em sequência, sem simultaneidade entre eles, e você pode ter certeza de que a infraestrutura da Kotlin se encarrega de estabelecer um borda de antes doIO acontecimento ao passar para o pool de encadeamentos e retornar à sua rotina de chamada.

Aqui está um exemplo equivalente que talvez pareça mais convincente:

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

Uma vez que delayé uma função suspensa, e como estamos usando o Defaultdistribuidor que é apoiado por um conjunto de encadeamentos, as linhas 1, 2 e 3 podem ser executadas em um encadeamento diferente. Portanto, sua pergunta sobre garantias de acontecer antes se aplica igualmente a este exemplo. Por outro lado, nesse caso, é (espero) completamente óbvio que o comportamento desse código é consistente com os princípios da execução seqüencial.

Marko Topolnik
fonte
11
Obrigado. Na verdade, é a parte depois de "descansar" que me motivou a fazer essa pergunta. Existe algum link para documentos que eu possa ler? Como alternativa, os links para o código-fonte onde isso acontece antes que a borda seja estabelecida também seriam de grande ajuda (associação, sincronização ou qualquer outro método).
23419 Vasiliy
11
Essa é uma garantia tão básica que eu nem perderia meu tempo conferindo. Sob o capô, tudo se resume executorService.submit()e existe um mecanismo típico de aguardar a conclusão da tarefa (concluir uma CompletableFutureou algo semelhante). Do ponto de vista de Kotlin coroutines, não há simultaneidade aqui.
Marko Topolnik 23/10/19
11
Você pode pensar na sua pergunta como análoga à pergunta "o sistema operacional garante que isso aconteça antes de suspender um thread e depois retomar em outro núcleo?" Os encadeamentos são para rotinas que são os núcleos da CPU para os encadeamentos.
Marko Topolnik 23/10/19
11
Obrigado pela sua explicação. No entanto, fiz esta pergunta para entender por que funciona. Entendo o seu ponto de vista, mas, até agora, não é a resposta rigorosa que estou procurando.
Vasiliy
2
Bem ... Na verdade, não acho que esse segmento tenha estabelecido que o código é seqüencial. Afirmou isso, certamente. Eu também estaria interessado em ver o mecanismo que garante que o exemplo se comporte conforme o esperado, sem afetar o desempenho.
G. Blake Meike
3

As corotinas em Kotlin fornecem acontecem antes das garantias.

A regra é: dentro de uma rotina, o código anterior a uma chamada de função de suspensão acontece antes do código após a chamada de suspensão.

Você deve pensar nas corotinas como se fossem threads regulares:

Embora uma corotina no Kotlin possa ser executada em vários threads, é como um thread do ponto de vista do estado mutável. Não há duas ações na mesma rotina que possam ser simultâneas.

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

Voltando ao exemplo de código. Capturar vars em corpos de função lambda não é a melhor ideia, especialmente quando lambda é uma corotina. O código anterior a um lambda não acontece antes do código interno.

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

Sergei Voitovich
fonte
A regra é a seguinte: o código anterior a uma chamada de função de suspensão acontece antes do código dentro da função de suspensão antes do código após a chamada de suspensão. Isso, por sua vez, pode ser generalizado para "a ordem do programa do código também é a ordem de acontecer antes do código ". Observe a ausência de algo específico para funções suspensas nessa instrução.
Marko Topolnik 23/10/19