ABA с программным обеспечением Clojure Transactional Memory

Мне было интересно, есть ли у Clojure встроенное решение проблемы ABA. Я создавал пример, который показывает эту проблему, но каким-то образом Clojure обнаруживает изменения. Это потому, что транзакции Clojure сравнивают ссылки, а не значения?

Мой пример:

(def x (ref 42))
(def io (atom false))
(def tries (atom 0))

(def t1 (Thread. (fn [] (dosync (commute x - 42)))))
(def t2 (Thread. (fn [] (dosync 
                          (Thread/sleep 100)
                          (commute x + 42)))))
(def t3 (Thread. 
  (fn [] 
    (dosync 
      (do 
        (Thread/sleep 1000) 
        (swap! tries inc) 
        (if (= 42 @x) 
          (reset! io true)))))))

(.start t3)
(.start t1)
(.start t2)

(.join t1)
@x
(.join t2)
@x
(.join t3)
@tries
(if (= true @io) (println "The answer is " @x))

Число попыток всегда равно 2, поэтому транзакция t3 должна была заметить изменения ref для t1 и t2. Кто-то знает причину такого поведения?

2 ответа

Прежде чем ответить на поставленный вопрос, позвольте мне сказать, что, безусловно, лучший источник информации о STM Clojure, помимо самого исходного кода, о котором мне известно, это статья Марка Фолькмана о программной транзакционной памяти (ссылка указывает на страницу журнала изменений, перейдите по ссылке на последнюю версию оттуда). Это невероятно всеобъемлющее. (Не беспокойтесь о отметке времени 2009 года, STM не сильно изменится.) Если вы хотите точно продумать, как все работает в таких сценариях, как этот, я настоятельно рекомендую прочитать его.


Что касается сценария под рукой:

Для чтения ссылки в транзакции STM обещает вернуть значение, которое было зафиксировано до попытки текущей транзакции. (Если, конечно, текущая попытка транзакции сама не установила значение в транзакции рассматриваемого Ref.) Это значение может быть или не быть самым последним значением, записанным в Ref, однако, если это не так, чтение должно быть доволен историей Ref. Если в истории ссылки нет такого значения, то для ссылки записывается ошибка, и транзакция повторяется. Впоследствии длина цепочки истории ссылки может быть увеличена из-за ошибки до максимальной длины истории ссылки (по умолчанию 10), хотя учтите, что это произойдет только при наличии возможности (еще одна запись в ссылку) и будет только помочь транзакциям, начатым "достаточно поздно" (чтобы их временные метки были позже, чем у меток некоторого значения, записанных в истории).

В случае под рукой, к тому времени t3 приступает к чтению ссылки, t1 а также t2 завершит свои записи в x без проблем и x больше не сможет удовлетворить запрос на чтение, требующий значения от t3 Первая попытка (Это потому, что цепочка истории ссылки по умолчанию начинается с длины 0, что означает, что исторические значения не сохраняются.) t3 должен записать ошибку для x и повторите попытку.

(Если вы повторно запустите три транзакции для одного и того же Ref и вспомогательного атома - скажем, снова вставив все, кроме верхних трех строк в ваш REPL - вы увидите tries прыгать, чтобы 4 во второй раз, а затем 5 на третьем указывает на то, что на данный момент доступна историческая ценность.)


По проблеме АБА:

Проблема ABA не относится к STM, потому что в надлежащем сценарии ABA буква "B" записывается в ячейку памяти (1) другим потоком и (2) после первого чтения "A" "главным" поток (тот, который должен страдать от проблемы ABA), а затем аналогично второй "A" записывается (1) другим потоком и (2) после записи "B", и оба "As" наблюдаются основной поток, но "B" - нет, но, как объяснено выше, в транзакции STM вы не можете наблюдать значение, записанное в Ref другим потоком после начала попытки транзакции, поэтому, если вы наблюдаете первое "A", Вы не сможете наблюдать "В" или второе "А".

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

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

Кроме того, обычно лучше всего использовать alter вместо commute, что легко ошибиться и обычно является случаем "преждевременной оптимизации".

Другие вопросы по тегам