В то время как переменные обеспечивают безопасное использование изменяемых хранилищ через изоляцию потоков, транзакционные ссылки обеспечивают безопасное коллективное использование изменяемых хранилищ с помощью программной транзакционной памяти (software transactional memory, SТМ). Ссылки привязаны к одному хранилищу в течении своей жизни и позволяют изменять его содержимое только внутри транзакции.
Понять транзакции Clojure должно быть просто, если вы когда-нибудь использовали транзакции баз данных - они гарантируют, что все действия со ссылками атомарны, консистентны и изолированы. Атомарны означает, что каждое изменение ссылок сделанное внутри транзакции либо происходит целиком либо не происходит вовсе. Консистентны означает, что каждое новое значение может быть проверено с помощью функции перед выполнением транзакции. Изолированы означает, что пока транзакция не завершена, другие транзацкции не увидят результат её работы. Еще одно свойство общее с STM (программной трензакционной памятью) - если во время выполнения транзакции возникает конфликт - она автоматически повторяется.
Существуют несколько способов реализации STM (программной транзакционной памяти) - блокирующая/пессимистичная, безблокировочная/оптимистическая, гибриды. В этом направлении разработки ведутся до сих пор. Для реализации STM Clojure использует управление конкурентным доступом с помощью многоверсионности (MVCC - multiversion concurrency control) с адаптивной историей для изоляции версий и предоставляет отдельную функцию commute.
На практике это означает:
-
Все операции чтения ссылок будут производиться в версии "мира ссылок", соответствующей началу транзакции (её "точка чтения"). Транзакции будут видеть все свои изменения, называемые значение-внутри-транзакции.
-
Все изменения, производимые со ссылками в течении транзакции (с помощью ref-set, alter или commute) будут применены в "мире ссылок" в один момент времени ("точке записи" транзакции).
-
Другие транзакции не смогут модифицировать ссылки, изменение которых было запрещено функциями ref-set / alter / ensure.
-
Другие транзакции могут модифицировать любые ссылки, изменение которых было разрешено функцией commute. Это не должно вызывать проблем, так как функция, применяемая commute должна быть коммутативна.
-
Операции чтения и объединения ссылок (с помощью commute) никогда не блокируют другие операции, а том числе и операции записи ссылок.
-
Операции записи не блокируют операции чтения и объединения.
-
Внутри транзакций необходимо избегать операций ввода/вывода, а также других действий, имеющих подобные "скрытые последствия", так как транзакции могут повторно исполняться. Макрос io! может быть использован, чтобы запретить выполнять подобные операции внутри транзакций.
-
Если условие корректности значения одной ссылки зависит от значения другой и при этом первая изменяется внутри транзакции, а вторая - нет, то вторая ссылка может быть "защищена" от изменений с помощью ensure. Защищенные таким образом ссылки не будут модифицироваться другими транзациями (пункт 3) и их значение не будет изменено в точке записи транзакции (пункт 2).
-
Реализация MVCC STM, используемая в Clojure создана для работы с персистентными коллекциями. Настоятельно рекомендуется использовать коллекции Clojure в качестве значений ссылок. Так как высока вероятность повторного исполнения транзакций, крайне необходима как можно более низкая цена копий и модификаций. Персистентные коллекции имеют "бесплатные" копии (оригинал просто переиспользуется, так как все равно не может быть изменен) и модификации коллекций эффективно переиспользуют структуры данных. В любом случае:
-
Значения, хранящиеся с ссылках, обязяны быть или рассматриваться как неизменяемые!! Иначе Clojure не поможет вам.