[Программирование, Kotlin] Корутины и синхронизация. Лучше не смешивать (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Предположим, у вас в коде есть критическая секция, которая не должна выполняться более, чем одним потоком одновременно.В мире Java одним из стандартных решений является добавление ключевого слова synchronized к сигнатуре метода. В Kotlin для получения того же эффекта используется аннотация @Synchronized
repeat(2) {
thread { criticalSection() }
}
@Synchronized
fun criticalSection() {
println("Starting!")
Thread.sleep(10)
println("Ending!")
}
Данный код выведет следующее:
Starting!
Ending!
Starting!
Ending!
Видно, что оба вызова отработали последовательно, один после другого.Теперь предположим, что мы хотим использовать корутины. Что у нас получится?
val scope = CoroutineScope(Job())
repeat(2) {
scope.launch { criticalSectionSuspending() }
}
@Synchronized
suspend fun criticalSectionSuspending() {
println("Starting!")
delay(10)
println("Ending!")
}
А получится, что вызовы критической секции пересекутся, что очень не здорово.
Starting!
Starting!
Ending!
Ending!
Понять, что же происходит, можно, разобравшись, как устроены корутины под капотом. Они реализованы с использованием подхода передачи продолжения. (Краткое объяснение можно посмотреть в моём докладе про корутины Grokking Coroutines, Dan Lew, а для более глубокого понимания рекомендую посмотреть доклад Романа Елизарова Deep Dive into Coroutines.)В рамках этой статьи всё, что вам надо знать — это то, что останавливаемые функции на самом деле не исполняются последовательно, строка за строкой. Когда останавливаемая функция доходит до точки остановки, она ставится на паузу и передаёт управление какой-то другой функции (позже, когда управление будет возвращено этой функции, — она продолжит выполнение).Таким образом, во втором примере кода на самом деле происходит следующее:1. criticalSectionSuspending() стартует, забирает блокировку и печатает Starting!2. Доходит до delay() (который является точкой остановки), выходит из функции и отдаёт блокировку.3. Так как блокировка свободна, начинается второй запуск criticalSectionSuspending(), которая забирает блокировку, печатает Starting!, останавливается и тоже отдаёт блокировку.4. Когда delay() заканчивается, criticalSectionSuspending() запускается снова, но уже с предыдущего места остановки.Для большей наглядности привожу временную диаграмму выполнения одной функции.
Как видите, время остановки проходит вне критической секции и её блокировка не удерживается. Именно поэтому разные потоки имеют доступ к синхронизированной останавливаемой функции — они выполняют её не одновременно.Это известная проблема в Kotlin. Фактически, компилятор не позволит использовать synchronized() {} с точкой останова внутри. Такой код не скомпилируется с ошибкой "The 'delay' suspension point is inside a critical section"Я убеждён, что в случае использования аннотации @Synchronized поведение компилятора должно быть аналогичным. Проблема заведена в YouTrack, но особых подвижек пока нет.Как же быть?Во-первых, следует признать, что проблема не в том, что «мы не можем использовать synchronized». synchronized — это просто средство обеспечения работоспособности критических секций. И единственная причина того, что у нас есть эти критические секции в том, что у нас есть общее изменяемое состояние. Соответственно, проблема звучит следующим образом: «нам нужен способ управления общим изменяемым состоянием в многопоточной среде».К счастью, у нас есть официальное руководство по Kotlin, в котором есть раздел Shared mutable state and concurrency, описывающий несколько неплохих вариантов действий. Для нас более всего подходит секция про мьютексы, так как они наиболее похожи на синхронизацию.
val mutex = Mutex()
val scope = CoroutineScope(Job())
repeat(2) {
scope.launch { criticalSectionSuspendingLocked() }
}
suspend fun criticalSectionSuspendingLocked() {
mutex.withLock {
println("Starting!")
delay(10)
println("Ending!")
}
}
Код выше работает ровно так, как нам нужно, — выводит не перемешанные сообщения.Я не хочу, чтобы люди считали, что использование мьютексов является универсальным решением при работе с общим изменяемым состоянием. В документации описано несколько подходов, и вы должны самостоятельно оценить, какой из них подходит к вашей конкретной ситуации. Но в случае, когда вы использовали синхронизированную функцию и теперь хотите её приостановить, мьютекс — это самый естественный выбор.От переводчика:Эту статью я нашёл во время поисков ответа на вопрос: Почему мьютекс в моём конкретном случае работает сильно медленнее (в полтора - два раза), чем синхронизация? Просто имейте в виду, если у вас нет точек останова в горячей критической секции, то лучше использовать синхронизацию, так как мьютекс может сильно просадить производительность.
===========
Источник:
habr.com
===========
===========
Автор оригинала: https://blog.danlew.net/author/dan-lew/
===========Похожие новости:
- [Разработка веб-сайтов, JavaScript, Программирование] JavaScript: что нас ждет в следующем году
- [Программирование, IT-стандарты, Управление сообществом] Безумная система
- [Программирование микроконтроллеров] STM32 и бесконтактный датчик температуры MLX90614. Подключение по I2C
- [Программирование, Учебный процесс в IT] Здравствуй, дорогой я двадцать лет назад
- [Программирование, .NET, ASP, C#] Разбираемся с middleware в ASP.NET Core (перевод)
- [Программирование, Машинное обучение] Ранжирование признаков с помощью Recursive Feature Elimination в Scikit-Learn (перевод)
- [Программирование, Разработка под MacOS, Компьютерное железо, Софт] Новым Mac с Apple M1 пока не хватает нативного софта и сред разработки
- [Java, Kotlin, Конференции] Lamoda x Joker 2020
- [Программирование, Учебный процесс в IT, Карьера в IT-индустрии, IT-компании] Вы безумны, остановитесь пока не поздно
- [Python, Программирование] Каверзные вопросы по Python
Теги для поиска: #_programmirovanie (Программирование), #_kotlin, #_kotlin, #_coroutines, #_funcorp, #_synchronized, #_mutex, #_multithreading, #_concurrency, #_syncronize, #_blog_kompanii_funcorp (
Блог компании FunCorp
), #_programmirovanie (
Программирование
), #_kotlin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:10
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Предположим, у вас в коде есть критическая секция, которая не должна выполняться более, чем одним потоком одновременно.В мире Java одним из стандартных решений является добавление ключевого слова synchronized к сигнатуре метода. В Kotlin для получения того же эффекта используется аннотация @Synchronized repeat(2) {
thread { criticalSection() } } @Synchronized fun criticalSection() { println("Starting!") Thread.sleep(10) println("Ending!") } Starting!
Ending! Starting! Ending! val scope = CoroutineScope(Job())
repeat(2) { scope.launch { criticalSectionSuspending() } } @Synchronized suspend fun criticalSectionSuspending() { println("Starting!") delay(10) println("Ending!") } Starting!
Starting! Ending! Ending! Как видите, время остановки проходит вне критической секции и её блокировка не удерживается. Именно поэтому разные потоки имеют доступ к синхронизированной останавливаемой функции — они выполняют её не одновременно.Это известная проблема в Kotlin. Фактически, компилятор не позволит использовать synchronized() {} с точкой останова внутри. Такой код не скомпилируется с ошибкой "The 'delay' suspension point is inside a critical section"Я убеждён, что в случае использования аннотации @Synchronized поведение компилятора должно быть аналогичным. Проблема заведена в YouTrack, но особых подвижек пока нет.Как же быть?Во-первых, следует признать, что проблема не в том, что «мы не можем использовать synchronized». synchronized — это просто средство обеспечения работоспособности критических секций. И единственная причина того, что у нас есть эти критические секции в том, что у нас есть общее изменяемое состояние. Соответственно, проблема звучит следующим образом: «нам нужен способ управления общим изменяемым состоянием в многопоточной среде».К счастью, у нас есть официальное руководство по Kotlin, в котором есть раздел Shared mutable state and concurrency, описывающий несколько неплохих вариантов действий. Для нас более всего подходит секция про мьютексы, так как они наиболее похожи на синхронизацию. val mutex = Mutex()
val scope = CoroutineScope(Job()) repeat(2) { scope.launch { criticalSectionSuspendingLocked() } } suspend fun criticalSectionSuspendingLocked() { mutex.withLock { println("Starting!") delay(10) println("Ending!") } } =========== Источник: habr.com =========== =========== Автор оригинала: https://blog.danlew.net/author/dan-lew/ ===========Похожие новости:
Блог компании FunCorp ), #_programmirovanie ( Программирование ), #_kotlin |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:10
Часовой пояс: UTC + 5