我写了一个函数来获取一个原子的旧值,同时放入一个新值,所有这些都在一个原子操作中:
(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永远不会离开函数,它是重置的最后一个值!那很重要。该功能似乎有效,但我还没有通过多个线程等彻底测试它。这个本地副作用是文档的安全例外,还是我错过了一些其他微妙的问题?
答案 0 :(得分:3)
是的,这将适用于Clojure中当前的原子实现,并且(几乎)保证按合同工作。
这里的关键是原子是 sychronous 。因此,内部swap!
保证在外swap!
之前完成。由于tmp
仅在本地使用,因此在单个线程中,内部swap!
也保证不会与另一个线程上的swap!
(tmp
)冲突。
虽然外swap!
(即swap!
的{{1}})可能与其他线程发生冲突,但当检测到冲突时,此at
将重试。由于swap!
是同步的,因此这些重试将在w.r.t中连续发生。调用swap!
的线程。我想可以想象,这最后一个条件不一定成立。例如,原子的实现可能可能在不同的线程上执行swap!
,并在检测到冲突时立即发出重试(无需等待先前的尝试完成) 。然而,这不是原子当前实现的方式,并且(在我看来)似乎不是实现原子的可能方式。
如果这个弱点困扰你,你可以使用比较和设置!代替:
swap!
答案 1 :(得分:0)
atom不能做你想做的事。
仅针对单个标识的未协调同步更新定义了原子。例如,用于更新原子的函数可能会运行很多次,因此无论您对该值执行什么操作,对于使其成为原子的每个值,可能会发生很多次。
代理通常是此类事情的更好选择,因为如果您向代理发送操作,它将运行at most once:
"At any point in time, at most one action for each Agent is being executed.
Actions dispatched to an agent from another single agent or thread will occur in the order they were sent"
另一种选择是向代理或原子添加watch,让该监视在发生后对每次更改做出反应。如果您能说服自己这些案例都不适合您,那么您可能已经找到了其中一个实际需要协调变更的案例,然后refs将是更好的工具,尽管这种情况很少见。通常手表的代理人或原子涵盖大多数情况。