Что происходит при создании вложенных вызовов dosync? Будут ли выполняться суб-транзакции в родительской области? Являются ли эти под-транзакции обратимыми, если родительская транзакция терпит неудачу?
Как ведут себя вложенные вызовы dosync?
Ответ 1
Если вы имеете в виду синтаксическое вложение, тогда ответ зависит от того, будет ли внутренний dosync
работать в том же потоке, что и внешний.
В Clojure, всякий раз, когда вводится блок dosync
, новая транзакция запускается, если она еще не была запущена в этом потоке. Это означает, что, хотя выполнение остается в одном потоке, внутренние транзакции можно считать включенными внешними транзакциями; однако, если a dosync
занимает позицию, синтаксически вложенную в другой dosync
, но, случается, запускается в новом потоке, у нее будет новая транзакция для себя.
Пример, который (надеюсь) иллюстрирует, что происходит:
user> (def r (ref 0))
#'user/r
user> (dosync (future (dosync (Thread/sleep 50) (println :foo) (alter r inc)))
(println :bar)
(alter r inc))
:bar
:foo
:foo
1
user> @r
2
"Внутренняя" транзакция повторяет после печати :foo
; "внешняя" транзакция никогда не должна перезапускаться. (Обратите внимание, что после этого происходит генерация цепочек истории r
, поэтому, если "большая" форма dosync
была оценена во второй раз, внутренний dosync
не будет повторять попытку. Он все равно не будет объединен с внешний, конечно.)
Кстати, Марк Волкманн написал фантастическую статью о Clojure Software Transactional Memory; это настоятельно рекомендуется прочитать для всех, кто заинтересован в том, чтобы получить глубокое понимание деталей такого рода.