理解Clojure中的STM属性

3

我正在学习《七周七并发模型》这本书。在书中,哲学家被表示为一些ref

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

每个哲学家的状态在dosync事务中翻转,以确保一致性,其中状态包括:thinking:eating
现在我想要一个线程来输出当前状态,这样我就可以确保状态始终有效:
(defn status-thread []
  (Thread.
    #(while true
      (dosync
        (println (map (fn [p] @p) philosophers))
        (Thread/sleep 100)))))

我们使用多个@来读取每个哲学家的值。当我们在哲学家之间进行map时,有些引用可能会发生改变。这会导致打印不一致的状态吗?
我知道Clojure使用MVCC来实现STM,但我不确定是否正确应用了它。
我的事务包含副作用,通常不应该出现在事务中。但在这种情况下,事务总是成功的,并且副作用只应该发生一次。这样做是否可以接受?
2个回答

1
您的交易并不真正需要副作用,如果您将问题扩展到足够大,我相信交易可能会因为历史不足而失败,并在有大量写入时重试副作用。我认为这里更合适的方式是将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 [p] @p) 等同于 deref
  2. 你应该避免在事务中使用 惰性计算,因为其中一些惰性值可能在 dosync 的上下文之外评估,或者根本不会评估。对于 map,你可以使用例如 doall,或者像这里一样仅使用急切评估的 mapv 变体,它生成一个向量而不是序列。

0

这种情况已经包含在STM设计中。

通过将代理与refs组合,可以明确解决此问题。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的作用)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接