Как синхронизация относится к корутинам
Что проверяют
Понимает ли кандидат, что корутины не отменяют гонок за общее состояние, и знает ли неблокирующие примитивы. Отдельный красный флаг для интервьюера: попытка использовать synchronized или блокирующий лок внутри suspend-функции.
Как отвечать
Корутина это не поток. Диспетчер (Default, IO) выполняет её на пуле потоков, и после точки приостановки корутина может продолжиться на другом потоке. Значит любое общее изменяемое состояние подвержено ровно тем же гонкам, что и в обычной многопоточности, включая проблемы видимости.
Способы, по возрастанию цены:
- Не шарить состояние. Неизменяемые данные, результат возвращается из
asyncилиwithContext, наружу отдаётся новое значение. Самый дешёвый вариант, и он же самый частый в проде. - Confinement. Все изменения делаются в одном контексте: на главном потоке или на однопоточном диспетчере. Тогда синхронизация не нужна вовсе.
- Атомарные операции.
AtomicIntegerдля счётчика,MutableStateFlow.update { }для состояния экрана: внутри цикл compare-and-set, лямбда должна быть чистой, потому что может выполниться несколько раз. kotlinx.coroutines.sync.Mutexдля критической секции, внутри которой есть suspend-вызовы.
class Counter {
private val mutex = Mutex()
private var count = 0
suspend fun increment() {
mutex.withLock { count++ }
}
suspend fun value(): Int = mutex.withLock { count }
}Особенности Mutex: он приостанавливает корутину, а не блокирует поток, и он не реентерабельный, вложенный withLock на том же мьютексе даёт дедлок. Для ограничения параллелизма N есть Semaphore.
withContext примитивом синхронизации не является, он переключает диспетчер. Взаимное исключение он даёт только в частном случае: когда целевой диспетчер однопоточный.
Чего делать нельзя: synchronized(this) { someSuspendCall() } не скомпилируется, потому что приостанавливаться внутри блока нельзя. А synchronized вокруг блокирующего вызова в корутине на Dispatchers.Default съедает поток небольшого пула и роняет пропускную способность. Блокирующие вызовы уводят на Dispatchers.IO, а лучше заменяют на Mutex.
Альтернатива локам: последовательная обработка через Channel по образцу actor, когда состоянием владеет одна корутина, а остальные шлют ей сообщения.
Где спотыкаются
Считают, что раз корутины лёгкие и код выглядит последовательным, гонок в нём не бывает. Либо переносят привычки из Java и ставят блокирующий лок в suspend-код.