Программная транзакционная память в Clojure предоставляет программисту простые, мощные и безопасные инструменты для многопоточного программирования. Эти инструменты сконструированы с учетом типичных ошибок и проблем в многопоточных программах. Например, Clojure STM гарантирует что взаимные блокировки никогда не возникнут в процессе её работы.
Но даже не смотря на то, что STM что-то гарантирует, при должном навыке можно умудриться нарваться на добрую парочку старых добрых граблей многопоточного программирования.
Взаимная блокировка, deadlock, возникает в многопоточной среде когда несколько потоков находятся в состоянии бесконечного ожидания ресурсов, захваченных самими этими потоками.
Например, для агента
(def a (agent nil))
попытка организовать взаимную блокировку в лоб обречена на провал — STM нам этого не позволит — агент не может ожидать окончания выполнения действия в самом действии:
(send-off a #(do (await a) %)) > (agent-error a) #<Exception java.lang.Exception: Can't await in agent action>
future
выносит ожидание окончания выполнения дейтсвия агента в
отдельный поток, который затем блокируется в потоке действия — происходит
взаимоблокировка потоков.
(send-off a #(do (deref (future (await a))) %))
Вуаля — наш агент впал в кому, и больше на сообщения не реагирует:
(send-off a (constantly true)) > a #<Agent@asdf: nil>