Надеюсь, каждый разработчик знает, что одновременный доступ к общему изменяемому состоянию - это плохо. Этот доступ должен быть синхронизирован или, что еще лучше, полностью исключен, иначе будут гарантированы всевозможные проблемы, которые трудно отладить. У всех есть четкое представление о том, что такое изменяемое состояние, но существует много путаницы и непонимания того, что в этом контексте означают одновременный и синхронизированный.
Например, рассмотрим следующий фрагмент кода Kotlin с использованием сопрограмм Kotlin:
val m = mutableMapOf<Int,String>() m[1] = "one" // (1) yield() // or other suspending function m[2] = "two" // (2)
У нас есть изменяемая карта m
, которая мутирует дважды, и вызов функции приостановки между мутациями. Сопрограмма, выполняющая этот код, будет приостановлена после строки (1) и может быть возобновлена в другом потоке для выполнения строки (2). Мы никак не синхронизировали нашу изменяемую карту, поэтому теперь одна и та же карта используется двумя разными потоками. Мы обречены?
Нет. Фактически, реализация функции приостановки yield
выполняет синхронизацию для нас, чтобы гарантировать, что код до yield
происходит до кода после yield
, чтобы он не был параллельным (т. е. последовательно).
Так что же такое одновременный? У этого слова есть точное определение. Две операции являются параллельными, если они не упорядочены отношением происходит до. Когда мы выполняем код в двух разных потоках, не имеет значения, сколько фактического времени проходит между двумя операциями. Параллелизм не определяется временем настенных часов. У нас могут быть операции (1) и (2), выполняющиеся с интервалом в десять секунд в разных потоках, и они все равно будут выполняться одновременно и вызывать у нас все виды ошибок, если между ними не существует связи произошло до.
Но откуда берется отношение происходит до? Что ж, когда мы пишем однопоточный код, все действия в потоке выполняются в программном порядке, поэтому все действия в данном потоке упорядочены, то есть последовательно . Изменять из одного потока всегда безопасно. Никакие два действия в одном потоке не могут быть одновременными.
Однако, когда у нас несколько потоков, все действия в разных потоках являются параллельными, если не используются операции синхронизации. Операции синхронизации устанавливают связь происходит раньше между операциями в разных потоках, что позволяет нам изменять общее состояние последовательным способом без введения одновременных мутации.
Несмотря на то, что сопрограмма в Kotlin может выполняться в нескольких потоках, она похожа на поток с точки зрения изменяемого состояния. Никакие два действия в одной сопрограмме не могут быть одновременными. Как и в случае с потоками, вам следует избегать совместного использования изменяемого состояния между сопрограммами, иначе вам придется самому беспокоиться о синхронизации.
Избегайте совместного использования изменяемого состояния. Ограничьте каждый изменяемый объект одним потоком или одной сопрограммой и хорошо спите.
дальнейшее чтение
Формальное определение отношения происходит до для мира JVM можно найти в Главе 17 JLS. Эта концепция была введена Лесли Лэмпортом в его статье 1978 года Время, часы и порядок событий в распределенной системе, и она стала стандартной формальной структурой для обсуждения параллелизма во всех видах программных систем. .