Jaminan Kotlin coroutine "terjadi sebelum"?

10

Apakah Kotlin coroutine memberikan jaminan "terjadi sebelum"?

Sebagai contoh, apakah ada jaminan "terjadi sebelum" antara menulis ke mutableVardan selanjutnya membaca (berpotensi) utas lain dalam kasus ini:

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

Edit:

Mungkin contoh tambahan akan memperjelas pertanyaan lebih baik karena itu lebih Kotlin-ish (kecuali untuk mutabilitas). Apakah kode ini 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)
Vasiliy
sumber
Perhatikan bahwa saat dijalankan pada JVM Kotlin menggunakan model memori yang sama dengan Java.
Slaw
1
@ Hukum, saya tahu itu. Namun, ada banyak keajaiban yang terjadi di bawah tenda. Oleh karena itu, saya ingin memahami apakah ada jaminan yang terjadi sebelum saya dapatkan dari coroutine, atau semuanya ada pada saya.
Vasiliy
Jika ada, contoh kedua Anda menyajikan skenario yang lebih sederhana: ia hanya menggunakan objek yang dibuat di dalam withContext, sedangkan contoh 1 menciptakannya terlebih dahulu, bermutasi di dalam withContext, dan kemudian membacanya withContext. Jadi contoh pertama menggunakan lebih banyak fitur keamanan benang.
Marko Topolnik
... dan kedua contoh hanya menjalankan aspek "urutan program" yang terjadi sebelum, yang paling sepele. Saya berbicara tentang level coroutine di sini, bukan JVM yang mendasarinya. Jadi pada dasarnya, Anda bertanya apakah korlin Kotlin rusak parah atau bahkan tidak memberikan pesanan program terjadi sebelumnya.
Marko Topolnik
1
@MarkoTopolnik, koreksi saya jika saya salah, tetapi JLS hanya menjamin "urutan program terjadi sebelum" untuk eksekusi pada utas yang sama. Sekarang, dengan coroutine, meskipun kodenya terlihat berurutan, dalam praktiknya ada beberapa mesin yang menurunkannya ke utas yang berbeda. Saya mengerti maksud Anda "ini adalah jaminan dasar sehingga saya bahkan tidak akan membuang waktu untuk memeriksanya" (dari komentar lain), tetapi saya mengajukan pertanyaan ini untuk mendapatkan jawaban yang keras. Saya cukup yakin bahwa contoh yang saya tulis aman, tetapi saya ingin mengerti mengapa.
Vasiliy

Jawaban:

6

Kode yang Anda tulis memiliki tiga akses ke status bersama:

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

Ketiga akses tersebut secara ketat dipesan secara berurutan, tanpa ada konkurensi di antara mereka, dan Anda dapat yakin bahwa infrastruktur Kotlin menangani membangun tepi sebelum terjadi ketika menyerahkan ke IOkolam utas dan kembali ke coroutine panggilan Anda.

Berikut ini contoh yang setara yang mungkin terlihat lebih meyakinkan:

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

Karena delaymerupakan fungsi yang dapat ditangguhkan, dan karena kami menggunakan Defaultdispatcher yang didukung oleh kumpulan utas, baris 1, 2 dan 3 masing-masing dapat dijalankan pada utas yang berbeda. Karenanya pertanyaan Anda tentang jaminan sebelum- berlaku berlaku sama untuk contoh ini. Di sisi lain, dalam hal ini (saya harap) benar-benar jelas bahwa perilaku kode ini konsisten dengan prinsip-prinsip eksekusi berurutan.

Marko Topolnik
sumber
1
Terima kasih. Ini sebenarnya bagian setelah "yakin" yang memotivasi saya untuk mengajukan pertanyaan ini. Apakah ada tautan ke dokumen yang bisa saya baca? Atau, tautan ke kode sumber tempat tepi yang terjadi sebelum ini juga akan sangat membantu (bergabung, sinkronisasi, atau metode lainnya).
Vasiliy
1
Ini adalah jaminan dasar sehingga saya bahkan tidak akan membuang waktu untuk memeriksanya. Di bawah tenda itu bermuara executorService.submit()dan ada beberapa mekanisme khas menunggu penyelesaian tugas (menyelesaikan CompletableFutureatau sesuatu yang serupa). Dari sudut pandang Kotlin coroutines 'tidak ada konkurensi sama sekali di sini.
Marko Topolnik
1
Anda dapat menganggap pertanyaan Anda sebagai analog dengan mengajukan "apakah OS menjamin kejadian sebelumnya sebelum menangguhkan utas dan kemudian melanjutkannya pada inti lain?" Thread adalah untuk menghubungkan apa core CPU ke thread.
Marko Topolnik
1
Terima kasih atas penjelasan anda Namun, saya mengajukan pertanyaan ini untuk memahami mengapa itu berhasil. Saya mengerti maksud Anda, tetapi, sejauh ini, itu bukan jawaban keras yang saya cari.
Vasiliy
2
Yah ... Saya sebenarnya tidak berpikir utas ini menetapkan bahwa kode tersebut berurutan. Itu telah menegaskannya, tentu saja. Saya juga akan tertarik melihat mekanisme yang menjamin bahwa contoh tersebut berperilaku seperti yang diharapkan, tanpa memengaruhi kinerja.
G. Blake Meike
3

Coroutine di Kotlin memang menyediakan terjadi sebelum jaminan.

Aturannya adalah: di dalam coroutine, kode sebelum panggilan fungsi ditunda terjadi sebelum kode setelah panggilan ditunda.

Anda harus memikirkan coroutine seolah-olah itu adalah utas biasa:

Meskipun coroutine di Kotlin dapat mengeksekusi di banyak utas, itu seperti utas dari sudut pandang keadaan yang bisa berubah. Tidak ada dua tindakan dalam coroutine yang sama dapat dilakukan bersamaan.

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

Kembali ke contoh kode. Menangkap vars di badan fungsi lambda bukanlah ide terbaik, terutama ketika lambda adalah coroutine. Kode sebelum lambda tidak terjadi sebelum kode di dalam.

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

Sergei Voitovich
sumber
Aturannya adalah ini, sebenarnya: kode sebelum panggilan fungsi ditunda terjadi - sebelum kode di dalam fungsi tunda, terjadi - sebelum kode setelah panggilan ditunda. Ini, pada gilirannya, dapat digeneralisasi ke "urutan program kode juga kode terjadi sebelum pesanan". Perhatikan tidak adanya sesuatu yang spesifik untuk fungsi yang dapat ditangguhkan dalam pernyataan itu.
Marko Topolnik