我对Clojure非常陌生,能否给我一些实际场景的解释呢?我指的是,在哪些情况下使用Ref、Var、Agent和Atom。我已经读过书籍,但仍然无法理解真实世界的例子。
我强烈推荐阅读《The Joy of Clojure》或《Programming Clojure》以获得此问题的真正答案,以下是每个选项的简要动机:
首先观看关于Identity概念的视频和/或在这里学习。
协调访问用于当两个Identities需要一起更改时,经典示例是将资金从一个银行账户转移到另一个银行账户,必须完全移动或根本不移动。
不协调访问用于仅需要更新一个Identity的情况,这是非常常见的情况。
同步访问用于在调用继续之前等待所有Identities稳定下来的情况。
异步访问是"发射并忘记",让Identity在自己的时间内达到新状态。
Ref是用于需要在线程之间进行同步的状态。如果您需要跟踪一堆不同的事物,并且有时需要执行同时写入几个事物的操作,则使用ref。每当您有多个不同的状态片段时,使用ref都不是一个坏主意。
Atom是用于需要在线程之间同步的独立状态。如果您永远不需要同时更改原子和其他任何内容的状态,则使用原子是安全的(特别是如果整个程序中只有一个状态片段,则可以将其放入原子)。作为非平凡的例子,如果您正在尝试缓存函数的返回值(即记忆化它),则使用原子可能是安全的-状态对函数外部的所有内容都是不可见的,因此您不必担心函数内部的状态更改会弄乱任何事情。
代理的主要点是它们在不同的线程中运行。您可以获取代理的值并告诉它应用一个函数到其值上,但是您不知道函数何时运行或者将被应用于哪个值。
Var用于需要按线程存储某些内容的情况。如果您有一个多线程程序,每个线程都需要自己的私有状态,请将该状态放在变量中。
就实际应用而言,如果您提供了要执行的示例,我们可以告诉您要使用什么。
当我第一次了解这些类型时,我也很难理解在哪里可以或应该使用它们,因此以下是我的简明英语答案:
使用 var 当数据不会改变时。每当您使用 def
或大多数以 def
开头的函数(例如 defn
)时,就会发生这种情况。
使用 atom 当您有一个单独的项目需要更改时。一个例子可能是计数器或您想要添加项目的向量。
使用 ref 当您有两个或更多必须同时更改的内容时。如果您熟悉“数据库事务”,请考虑此类操作。这种情况的典型示例是将资金从一个帐户转移到另一个帐户。每个帐户都可以存储在 ref 中,以便进行的更改看起来像原子操作。
使用 agent 当您想要更改某些内容但不关心时间问题。这可能是长时间计算或将某些内容写入文件或套接字。请注意,对于后者,您应该使用 send-off
。
注意:我知道每种类型还有更多要素,但希望这能为您提供一个起点。
我写了一篇文章总结了它们之间的区别,并帮助选择何时使用哪个。
共享状态 - 何时使用 vars、atoms、agents 和 refs?
我希望这将帮助那些在这个话题上寻找答案的人。
以下是来自文章中的一些快捷方式,经过 @tunaci 建议后:
Vars
Vars 对于每个线程都是全局的。
创建后不要更改 vars。技术上是可能的,但由于许多原因,这是一个不好的想法。
Atoms
为每个线程共享可变状态的访问。更改同步发生。当其他线程在运行期间更改状态时,请重试。
不要使用非幂等函数和执行时间长的函数。
Agents
为每个线程共享可变状态的访问。更改异步发生。
Refs
Refs 的工作方式类似于数据库事务。写入和读取在 dosync 中受到保护。您可以在事务中安全地操作多个 refs。
请查看网站上的图片,因为总是可能有一些更新。
这是一个复杂而长的话题,如果没有复制并粘贴文章,我很抱歉无法给出完整的答案,所以请原谅我引导您访问该网站 :)
原子、引用和代理——这里有些启示。
http://blog.jayfields.com/2011/04/clojure-state-management.html
state-a
,但在这样做时要引用state-b
,我仍然需要一个ref
,对吗?所以它不是同时改变多个事物,而是在改变其中任何一个时引用多个事物? - event_jrensure
函数:http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/ensure 来使这更明确和高效。 - Arthur Ulfeldt