我应该在我的交换功能之外还是内部运行检查!



当我要有条件地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)))))

最新更新