当我要有条件地swap!
原子的值时,条件应该包装swap!
还是应该成为swap!
调用的函数的一部分?
(import '(java.time Instant))
(def not-nil? (comp not nil?))
(defonce users (atom {
"example user 1" {:ts (Instant/now)}
"example user 2" {:ts (Instant/now)}
}))
(defn update-ts [id]
(if (not-nil? (get @users id))
(swap! users assoc-in [id :ts] (Instant/now))))
在上面的例子中,我在执行swap!
之前为用户进行存在检查。 但是,在检查后但在swap!
之前,不能从users
中删除用户吗? 那么,将检查放在由swap!
运行的函数中是否更安全?
(defn update-ts [id]
(swap! users (fn [users]
(if (not-nil? (get users id))
(assoc-in users [id :ts] (Instant/now))
users))))
但是在检查后但在
swap!
之前不能从用户中删除用户吗?那么,将检查放在swap!
运行的函数中是否更安全?
是的,完全正确。你永远不应该决定如何从任何地方改变原子,而不是在该原子的swap!
内部。由于swap!
是唯一保证原子的操作,因此每次您不这样做(即,从swap!
外部对原子做出决定)时,您都会引入竞争条件。
后无法从用户中删除用户,但是 在交换之前!?那么,将支票放入 函数由交换运行!?
正如amalloy所说,如果你需要它是bulletproot,你必须将not-null?
检查放在交换函数中。
但是,请记住,您和您的团队正在编写程序的其余部分。因此,您有很多外部信息可以简化您的决定:
如果你只有一个线程(像大多数程序一样),你永远不需要担心竞争条件。
如果您有 2 个或更多线程,也许您永远不会从映射中删除条目(它只会累积
:ts
值)。那么你也不需要担心冲突。如果您的函数比上面的简单示例更复杂,您可能希望使用
(dosync ...)
窗体来包装多个步骤,而不是将所有内容硬塞到单个swap
函数中。
在第三种情况下,将atom
替换为ref
。一个例子是:
(defonce users (ref {...} )) ; ***** must us a ref here *****
(dosync
(if (not-nil? (get @users id))
<lots of other stuff...>
(alter users assoc-in [id :ts] (Instant/now)))))