Clojure atom 中的长时间运行函数

Long running function in Clojure atom

我有一个函数可以加载大量用户(这需要一段时间)并将它们存储在一个原子中。我想知道将用户加载到 let 绑定然后重置原子或仅将它们加载到原子重置之间是否有任何区别!功能?

(let [all-users (get-users)]
    (reset! users all-users))

(reset! users (get-users))

使用reset!时没有区别。但是,当使用其他对原子进行操作的函数时,您应该小心,因为值生成函数可能会被多次调用。例如,swap!.

就是这种情况

它们是一样的,原因如下

由于 reset! 是一个函数,对 (reset! users (get-users)) 的调用将与 Clojure 中的任何其他函数调用行为相同:调用中的每个 S 表达式都将被计算,然后作为参数传递到功能。这意味着 (get-users) 的评估将首先发生 ,并将结果传递给 reset!。因此,这将与 let 形式完全一样。

对比swap!

这些担忧在 swap! 中发挥作用。因为您发送 swap! 一个在事务内部调用的函数,所以您可以更好地控制您的长 运行ning 作业是在事务内部还是外部发生。例如,如果您有函数 poll-users-updatesupdate-users-from-poll,您可以将对第一个函数的调用设置为在事务内部或外部发生:

; outside the transaction
(swap! users update-users-from-poll (poll-users-updates))
; inside the transaction
(swap! users (fn [users] (update-users-from-poll users (poll-users-updates))))

此处的第二种形式更有可能必须重新启动,因为更新功能需要更长的时间运行,为其他写入原子以强制重新启动留出更多时间。

相比之下,第一种形式不太可能强制重试,因此通常更受欢迎。另一方面,如果您的 poll-users-updates 函数还需要对 users 数据的当前状态进行操作(例如,查找最近更新的用户的时间戳,以便进行轮询更有效),那么第二种方法可能是首选,因为它可以确保您在进行民意调查时拥有 users 的最新值。

关于重试和副作用

相对于 STM is that your update functions may be called multiple times. Saying that side-effecting functions are "dangerous inside atoms" is perhaps a bit strong. They can be dangerous though, and it's best to assume they are. Even when they aren't though (such as when effects are idempotent 的突出显示,这意味着您调用一次和多次调用相同的东西),最好让它们没有副作用。 Clojure 的引用和原子都是如此,它们会在发生冲突时重试。相反,代理没有重试语义,所以在发送给代理的函数中有副作用是可以的。由于代理将更新功能排队并运行按顺序发送它们,因此不会发生冲突,因此无需重试。