Котлінські супроти гарантій "трапляються раніше" гарантують?


10

Чи надають супроводи Котліна які-небудь гарантії "раніше"?

Наприклад, чи існує гарантія "перед тим", що між записом mutableVarі наступним читанням (потенційно) іншого потоку в цьому випадку гарантує :

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

Редагувати:

Можливо, додатковий приклад уточнить питання краще, тому що це більше Котлін-іш (за винятком змінності). Чи безпечний цей код:

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

private data class Data(var data: Int)

Зауважте, що при запуску на JVM Kotlin використовує ту саму модель пам'яті, що і Java.
Слоу

1
@Slaw, я це знаю. Однак, під капотом відбувається багато магії. Тому я хотів би зрозуміти, чи є якісь випадки - перш ніж гарантії, які я отримую від судових процедур, чи це все на мене.
Василь

Якщо що-небудь, у вашому другому прикладі є ще простіший сценарій: він просто використовує об'єкт, створений всередині withContext, тоді як перший приклад створює його спочатку, мутує всередині withContext, а потім читає після withContext. Таким чином, 1-й приклад виконує інші функції безпеки ниток.
Марко

... і обидва приклади здійснюють лише аспект "програмного порядку", що трапляється раніше, найтривіальніший. Я говорю про рівень судових процедур, а не про базовий JVM. Таким чином, ви запитуєте, чи такі порушення Котліна настільки сильно порушені, що вони навіть не надають програмного замовлення.
Марко

1
@MarkoTopolnik, виправте мене, якщо я помиляюся, але JLS гарантує лише "замовлення на програму відбувається раніше" для виконання на тій же темі. Тепер із супрограмами, хоча код і виглядає послідовно, на практиці є певна техніка, яка завантажує його в різні потоки. Я розумію вашу думку "це така основна гарантія, що я навіть не витрачав би час на її перевірку" (з іншого коментаря), але я попросив це питання, щоб отримати жорстку відповідь. Я майже впевнений, що приклади, які я написав, є безпечними для потоків, але я хочу зрозуміти, чому.
Василь

Відповіді:


6

Код, який ви написали, має три доступу до загального стану:

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

Три звернення суворо послідовно впорядковані, між ними не виникає суперечностей, і ви можете бути впевнені, що інфраструктура Котліна піклується про встановлення попереднього краю при передачі в IOпул потоків і назад до вашої програми виклику.

Ось еквівалентний приклад, який може виглядати більш переконливо:

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

Так delayяк це функція, що припиняється, і оскільки ми використовуємо Defaultдиспетчер, який підтримується пулом потоків, рядки 1, 2 і 3 можуть виконуватись в іншому потоці. Тому ваше питання про гарантії, що відбудуться раніше, однаково стосується цього прикладу. З іншого боку, в даному випадку абсолютно очевидно, що поведінка цього коду відповідає принципам послідовного виконання.


1
Дякую. Насправді ця частина після "будьте впевнені" мотивувала мене задати це питання. Чи є посилання на документи, які я міг прочитати? Альтернативно, посилання на вихідний код, де це відбувається перед тим, як встановлено край, також допоможуть (приєднання, синхронізація чи будь-який інший метод).
Василь

1
Це така основна гарантія, що я навіть не витрачав би час на перевірку. Під кришкою вона опускається executorService.submit()і є якийсь типовий механізм очікування на виконання завдання (завершення CompletableFutureчи щось подібне). З точки зору котлінських процедур, тут взагалі немає ніякої суперечності.
Марко

1
Ви можете вважати своє запитання аналогічним запитанню "гарантує ОС оперативне ставлення до того, як призупинити потік і потім відновити його на іншому ядрі?" Нитки полягають у спрощенні того, що є ядрами процесора.
Марко

1
Дякуємо за ваше пояснення. Однак я поставив це питання, щоб зрозуміти, чому воно працює. Я бачу вашу думку, але поки що це не сувора відповідь, яку я шукаю.
Василь

2
Ну ... я насправді не думаю, що цей потік встановив, що код є послідовним. Це, безумовно, стверджувало. Мені теж було б цікаво побачити механізм, який гарантує, що приклад поводиться так, як очікувалося, не впливаючи на результативність.
Г. Блейк

3

Програми в Котліні дійсно стають перед гарантіями.

Правило таке: всередині підпрограми код до виклику призупинення функції відбувається перед кодом після призупинення виклику.

Вам слід подумати про співпраці як би вони були звичайними нитками:

Навіть незважаючи на те, що програма в Котліні може виконуватись на декількох потоках, це подібно до потоку з точки зору стану, що змінюється. Жодна дія в одній програмі не може бути одночасною.

Джерело: https://proandroiddev.com/what-is-concurrent-access-to-mutable-state-f386e5cb8292

Повернення до прикладу коду. Захоплення вар в тілах функцій лямбда - не найкраща ідея, особливо, коли лямбда - це супровід. Код до лямбда не відбувається перед кодом всередині.

Дивіться https://youtrack.jetbrains.com/issue/KT-15514


Правило таке: насправді: код до виклику призупинення функції відбувається - перед кодом всередині функції призупинення, відбувається - перед кодом після призупинення виклику. Це, у свою чергу, може бути узагальнено до "замовлення програмного коду - це також код, що відбувається перед замовленням". Зауважте, що в цьому операторі немає нічого конкретного для призупиняються функцій.
Марко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.