Clojure原子(atom)中的长时间运行函数

3

我有一个函数,它会加载很多用户(需要一段时间)并将它们存储在原子中。我想知道将用户加载到let绑定中然后重置原子和直接在原子reset!函数中加载它们之间是否有任何区别?

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

或者

(reset! users (get-users))
2个回答

6

它们是相同的,原因如下

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

swap!形成对比

这些问题在swap!中发挥作用。因为您向swap!发送要在事务内调用的函数,所以您可以更好地控制长时间运行的作业是在事务内还是在事务外发生。例如,如果您有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 相关的问题,即您的更新函数可能会被多次调用。说副作用函数在原子内部是“危险的”可能有点过分了。它们可能会很危险,最好假设它们是危险的。即使它们不是(例如,当效果是幂等的时,意味着一次调用和多次调用得到的结果相同),也最好让它们没有副作用。这对Clojure的引用和原子都是正确的,在冲突的情况下重试。相反,代理没有重试语义,因此在发送到代理的函数中具有副作用是可以的。由于代理排队更新函数并按顺序运行它们,因此不会发生冲突,因此无需重试。

3

使用reset!时不会有任何差异。但是,当使用其他操作原子的函数时,要小心,因为产生值的函数可能会被调用多次。例如,在swap!中就是这种情况。


1
有趣的是,具有副作用的函数在原子中很危险。 - shmish111

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接