Clojure中Ref、Var、Agent、Atom的区别及其示例

120

我对Clojure非常陌生,能否给我一些实际场景的解释呢?我指的是,在哪些情况下使用Ref、Var、Agent和Atom。我已经读过书籍,但仍然无法理解真实世界的例子。

5个回答

182

我强烈推荐阅读《The Joy of Clojure》或《Programming Clojure》以获得此问题的真正答案,以下是每个选项的简要动机:

首先观看关于Identity概念的视频和/或在这里学习

  • Refs用于协调同步地访问"多个Identities"。
  • Atoms用于不协调同步地访问单个Identity。
  • Agents用于不协调异步地访问单个Identity。
  • Vars用于具有共享默认值的隔离身份的线程本地访问。

协调访问用于当两个Identities需要一起更改时,经典示例是将资金从一个银行账户转移到另一个银行账户,必须完全移动或根本不移动。

不协调访问用于仅需要更新一个Identity的情况,这是非常常见的情况。

同步访问用于在调用继续之前等待所有Identities稳定下来的情况。

异步访问是"发射并忘记",让Identity在自己的时间内达到新状态。


在协调访问中,如果我只想改变 state-a,但在这样做时要引用 state-b,我仍然需要一个 ref,对吗?所以它不是同时改变多个事物,而是在改变其中任何一个时引用多个事物? - event_jr
2
是的,你理解得很正确,如果你希望 state-a 和 state-b 的新值基于 a 和 b 值的一致组合,那么它们都必须是 refs。你需要在 state-a 和 state-b 一致的上下文中计算出新值。当它们都是 refs 时,如果 b 在中途发生了变化,那么事务将重新启动并使用 a 和 b 的新值。考虑使用 ensure 函数:http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/ensure 来使这更明确和高效。 - Arthur Ulfeldt
3
也许可以添加一个“孤立但共享默认设置”的解释来补充答案? - Didier A.
1
“Coordinated access is used when two Identities need to be changed together...” 这句话中的“changed”是否正确? - Carcigenicate
clojure-docs的链接已经失效了。正确的链接是https://clojure-doc.org/articles/language/concurrency_and_parallelism/。 - anton0xf

43

Ref是用于需要在线程之间进行同步的状态。如果您需要跟踪一堆不同的事物,并且有时需要执行同时写入几个事物的操作,则使用ref。每当您有多个不同的状态片段时,使用ref都不是一个坏主意。

Atom是用于需要在线程之间同步的独立状态。如果您永远不需要同时更改原子和其他任何内容的状态,则使用原子是安全的(特别是如果整个程序中只有一个状态片段,则可以将其放入原子)。作为非平凡的例子,如果您正在尝试缓存函数的返回值(即记忆化它),则使用原子可能是安全的-状态对函数外部的所有内容都是不可见的,因此您不必担心函数内部的状态更改会弄乱任何事情。

代理的主要点是它们在不同的线程中运行。您可以获取代理的值并告诉它应用一个函数到其值上,但是您不知道函数何时运行或者将被应用于哪个值。

Var用于需要按线程存储某些内容的情况。如果您有一个多线程程序,每个线程都需要自己的私有状态,请将该状态放在变量中。

就实际应用而言,如果您提供了要执行的示例,我们可以告诉您要使用什么。


37

当我第一次了解这些类型时,我也很难理解在哪里可以或应该使用它们,因此以下是我的简明英语答案:

使用 var 当数据不会改变时。每当您使用 def 或大多数以 def 开头的函数(例如 defn)时,就会发生这种情况。

使用 atom 当您有一个单独的项目需要更改时。一个例子可能是计数器或您想要添加项目的向量。

使用 ref 当您有两个或更多必须同时更改的内容时。如果您熟悉“数据库事务”,请考虑此类操作。这种情况的典型示例是将资金从一个帐户转移到另一个帐户。每个帐户都可以存储在 ref 中,以便进行的更改看起来像原子操作。

使用 agent 当您想要更改某些内容但不关心时间问题。这可能是长时间计算或将某些内容写入文件或套接字。请注意,对于后者,您应该使用 send-off

注意:我知道每种类型还有更多要素,但希望这能为您提供一个起点。


1
非常感谢您清晰的回复 :-) 对像我这样的Clojure新手有很大帮助。 - gosukiwi

31

我写了一篇文章总结了它们之间的区别,并帮助选择何时使用哪个。

共享状态 - 何时使用 vars、atoms、agents 和 refs?

我希望这将帮助那些在这个话题上寻找答案的人。

以下是来自文章中的一些快捷方式,经过 @tunaci 建议后:

Vars

Vars 对于每个线程都是全局的。

创建后不要更改 vars。技术上是可能的,但由于许多原因,这是一个不好的想法。

Atoms

为每个线程共享可变状态的访问。更改同步发生。当其他线程在运行期间更改状态时,请重试。

不要使用非幂等函数和执行时间长的函数。

Agents

为每个线程共享可变状态的访问。更改异步发生。

Refs

Refs 的工作方式类似于数据库事务。写入和读取在 dosync 中受到保护。您可以在事务中安全地操作多个 refs。

何时使用哪个的流程图: flowchart

请查看网站上的图片,因为总是可能有一些更新。

这是一个复杂而长的话题,如果没有复制并粘贴文章,我很抱歉无法给出完整的答案,所以请原谅我引导您访问该网站 :)


5

刚准备添加那个链接。哈! - n3wb

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