Действительно, это то же самое, что и любая * проблема параллелизма : у вас есть несколько потоков управления, и он не определяет, какие операторы для каких потоков выполняются когда. Это означает, что в программе имеется большое количество ПОТЕНЦИАЛЬНЫХ путей выполнения, и ваша программа должна быть корректной для всех из них.
В общем случае проблема может возникать, когда состояние разделяется между потоками (в прошлом «легковесные процессы»). Это происходит при наличии общих областей памяти,
Чтобы убедиться в правильности, вам нужно убедиться, что эти области данных обновляются таким образом, чтобы не вызывать ошибок. Для этого вам необходимо определить «критические разделы» программы, где должна быть гарантирована последовательная работа. Это может быть всего лишь одна инструкция или строка кода; если язык и архитектура гарантируют, что они атомные , то есть не могут быть прерваны, то вы - золотой.
В противном случае вы идентифицируете этот раздел и ставите на него каких-то охранников. Классический способ - использовать семафор , который является атомарным утверждением, которое допускает только один поток управления за раз. Они были изобретены Эдсгаром Дейкстрой, так же как и имена, пришедшие из Голландии, P и V . Когда вы приходите к P , может продолжаться только один поток; все остальные потоки находятся в очереди и ожидают, пока исполняющий поток не перейдет к связанной операции V .
Поскольку эти примитивы немного примитивны, а голландские имена не очень интуитивны, было разработано несколько более масштабных подходов.
Пер Бринч-Хансен изобрел монитор , который в основном представляет собой просто структуру данных, в которой есть операции, гарантированные атомарные; они могут быть реализованы с семафорами. Мониторы - это почти то, на чем основаны операторы Java synchronized
; они заставляют объект или кодовый блок иметь такое специфическое поведение - то есть, только один поток может быть «внутри» их одновременно - с более простым синтаксисом.
Возможны и другие модалы. Haskell и Erlang решают проблему, будучи функциональными языками, которые никогда не позволяют изменять переменную после ее создания; это означает, что им, естественно, не нужно беспокоиться о синхронизации. Некоторые новые языки, такие как Clojure, вместо этого имеют структуру под названием «транзакционная память», что в основном означает, что когда является присваиванием, вы гарантированно присваиваете атомарное и обратимое назначение.
Так вот в двух словах. Чтобы по-настоящему узнать об этом, лучше всего взглянуть на тексты операционных систем, например, текст Энди Танненбаума .