Multithreading Clojure原子派生和重置原子

Multithreading Clojure原子派生和重置原子,multithreading,concurrency,clojure,Multithreading,Concurrency,Clojure,我编写了一个函数来获取原子的旧值,同时在一个原子操作中输入一个新值: (defn get-and-reset! [at newval] "Resets atom to newval and returns the old value. Atomic." (let [tmp (atom [])] (swap! at #(do (reset! tmp %) newval)) @tmp)) 文件上说交换!函数不应该有副作用,因为它可以被多次调用。这本身似乎不是问题,因为tmp

我编写了一个函数来获取原子的旧值,同时在一个原子操作中输入一个新值:

(defn get-and-reset! [at newval]
  "Resets atom to newval and returns the old value. Atomic."
  (let [tmp (atom [])]
    (swap! at #(do (reset! tmp %) newval))
    @tmp))

文件上说交换!函数不应该有副作用,因为它可以被多次调用。这本身似乎不是问题,因为tmp从未离开函数,而且它是最后一个重置的值!这很重要。这个函数似乎可以工作,但我还没有用多个线程对它进行彻底测试,等等。这个局部副作用是文档中的一个安全例外,还是我遗漏了其他一些微妙的问题

atom无法完成您试图完成的任务

原子只为单个身份的不协调同步更新定义。例如,用于更新原子的函数可能会运行很多次,因此,无论您对该值执行什么操作,都可能会对进入原子的每个值执行多次

对于这类事情,代理通常是更好的选择,因为如果您向代理发送操作,它将运行:


另一种选择是向代理或原子添加一个表,并让该表在每次更改发生后做出反应。如果你能说服自己这两种情况对你都不起作用,那么你可能已经发现其中一种情况实际上需要协调变化,那么参考文献将是更好的工具,尽管这是罕见的。通常,带手表的代理或原子会覆盖大多数情况

是的,这将与Clojure中原子的当前实现一起工作,并且(几乎)通过合同保证工作

这里的关键是原子是同步的。因此,内部
交换保证在外部
交换之前完成。由于
tmp
仅在本地使用,因此从单个线程开始,内部
交换也保证不会与
交换冲突(属于
tmp

而外部
交换(即
at
交换!
)可能与其他线程冲突,此
交换将在检测到冲突时重试。自从<代码>交换是同步的,这些重试将连续进行。线程将
交换。我想这最后一个条件不一定成立是可以想象的。例如,原子的实现可以执行
交换,并在检测到冲突后立即重试(无需等待以前的尝试完成)。然而,这不是目前实现原子的方式,而且(在我看来)似乎不是一种很可能实现原子的方式

如果这个缺点困扰你,你可以使用比较和设置!相反:

(defn get-and-reset! [at newval]
  "Resets atom to newval and returns the old value. Atomic."
  (loop [oldval @at]
    (if (compare-and-set! at oldval newval)
      ;; then (no conflict => return oldval)
      oldval
      ;; else (conflict => retry)
      (recur @at))))

这可能更适合。既然您没有使用当前(即将旧)值,为什么操作必须是原子的?如果使用旧值计算新值,因此需要事务,则操作需要是原子的。但您只是重置一个值,而不使用先前的值。你真正需要做的就是
(让[old@at](reset!at newval)oldval)
。。或者,我没有领会你的意图吗?@Josh看起来他确实使用了“当前(即将过时)”值,只是在调用之外使用了它,并且希望看到atom拥有的所有值。根据您提出的解决方案,可能会出现问题。假设有两个(或更多)线程调用
get和reset。如果他们两个都在对方可以重置之前解除原子的释放,它们都将返回相同的值,这意味着atom所拥有的其中一个值将永远不会被返回。看起来您正在尝试获得与
重置相同的值,但返回旧值。您可能想看看:and。@HoagyCarmichael是的,有一个竞争条件,我想我可以看到需要返回“正确”的旧值的情况,尽管这似乎是任意的,因为旧值不用于设置新值。实际上
交换将被使用,但如果我们不使用它,而是使用了
(reset!at(inc oldval))
,那么我们当然需要同步。但我们基本上“抛弃”了旧的价值观,所以它被阅读的那一刻似乎并不重要。在许多用例中,我的建议都是完全有效的,但对于一些人来说,如果返回的旧值的顺序很重要,那么它可能不会有效,而Toms肯定可以做到这一点。请参阅@jszakmeister提到的针对这个特定用例的说明。
(defn get-and-reset! [at newval]
  "Resets atom to newval and returns the old value. Atomic."
  (loop [oldval @at]
    (if (compare-and-set! at oldval newval)
      ;; then (no conflict => return oldval)
      oldval
      ;; else (conflict => retry)
      (recur @at))))