Понимание свойств STM в Clojure

Я просматриваю книгу 7 concurrency models in 7 weeks. В нем философы представлены в виде числа ref:

(def philosophers (into [] (repeatedly 5 #(ref :thinking))))

Состояние каждого философа переключается между :thinking и :eating с использованием dosync транзакций для обеспечения согласованности.

Теперь я хочу иметь поток, который выводит текущий статус, чтобы я мог быть уверен, что состояние действительно всегда:

(defn status-thread []
  (Thread.
    #(while true
      (dosync
        (println (map (fn [p] @p) philosophers))
        (Thread/sleep 100)))))

Мы используем несколько @ для чтения значений каждого философа. Может случиться так, что некоторые ссылки будут изменены, поскольку мы map над философами. Не приведет ли это к тому, что мы будем печатать противоречивое состояние, хотя у нас его нет?

Я знаю, что Clojure использует MVCC для реализации STM, но я не уверен, что применяю его правильно.

Моя транзакция содержит побочные эффекты, и обычно они не должны появляться внутри транзакции. Но в этом случае транзакция всегда будет успешной, а побочный эффект должен иметь место только один раз. Это приемлемо?


person Konstantin Milyutin    schedule 25.07.2016    source источник


Ответы (2)


Ваша транзакция на самом деле не нуждается в побочном эффекте, и если вы достаточно масштабируете проблему, я полагаю, что транзакция может завершиться неудачей из-за отсутствия истории и повторить побочный эффект, если будет много написания. Я думаю, что более подходящим способом здесь было бы приблизить dosync. Сделка должна быть чистой миссией по сбору фактов без побочных эффектов. Как только это привело к значению, вы можете выполнять с ним побочные эффекты, не затрагивая STM.

(defn status-thread []
  (-> #(while true
         (println (dosync (mapv deref philosophers)))
         (Thread/sleep 100))
    Thread.
    .start)) ;;Threw in starting of the thread for my own testing

Несколько вещей, которые я хочу упомянуть здесь:

  1. @ — это макрос чтения для deref fn, поэтому (fn [p] @p) эквивалентен просто deref.
  2. Вам следует избегать ленивости внутри транзакций, так как некоторые из ленивых значений могут оцениваться вне контекста dosync или вообще не оцениваться. Для mappings это означает, что вы можете использовать, например. doall, или, как здесь, только вариант mapv, получивший высокую оценку, который создает вектор, а не последовательность.
person Magos    schedule 25.07.2016

Это непредвиденное обстоятельство было включено в проект STM.

Эта проблема явно решается объединением агентов с ссылками. refs гарантируют, что все сообщения, заданные агентам в транзакции, отправляются ровно один раз и только при фиксации транзакции. Если транзакция будет повторена, они будут удалены и не отправлены. Когда транзакция в конечном итоге пройдет, они будут отправлены в момент фиксации транзакции.

(def watcher (agent nil))

(defn status-thread []
  (future
   (while true
     (dosync
      (send watcher (fn [_] (println (map (fn [p] @p) philosophers))))
      (Thread/sleep 100)))))

STM гарантирует, что ваша транзакция не будет зафиксирована, если ссылки, которые вы разрешили во время транзакции, изменились несовместимым образом во время ее выполнения. Вам не нужно явно беспокоиться об разыгрывании нескольких ссылок в транзакции (для чего был создан STM)

person Arthur Ulfeldt    schedule 25.07.2016