Clojure 可变存储类型
-
06-07-2019 - |
题
我正在尝试从网站上提供的 API 和文档来学习 Clojure。我对 Clojure 中的可变存储有点不清楚,我想确保我的理解是正确的。如果我有任何错误的想法,请告诉我。
编辑:当我收到关于其正确性的评论时,我正在更新它。
免责声明:所有这些信息都是非正式的,并且可能是错误的。不要使用这篇文章来了解 Clojure 的工作原理。
瓦尔斯 始终包含根绑定和可能的每线程绑定。它们与命令式语言中的常规变量相当,不适合在线程之间共享信息。 (感谢阿瑟·乌尔费尔特)
参考文献 是支持原子事务的线程之间共享的位置,原子事务可以更改单个事务中任意数量的引用的状态。事务在退出同步表达式 (dosync) 时提交,并使用 STM 魔法(回滚、队列、等待等)自动解决冲突
代理商 这些位置通过分派独立的操作函数来更改代理的状态,从而能够以最小的开销在线程之间异步共享信息。代理会立即返回,因此是非阻塞的,尽管在分派函数完成之前不会设置代理的值。
原子 是可以在线程之间同步共享的位置。它们支持不同线程之间的安全操作。
以下是我根据何时使用这些结构进行的友好总结:
- 变量就像命令式语言中常规的旧变量。(尽可能避免)
- Atom 类似于 Var,但具有线程共享安全性,允许立即读取和安全设置。 (感谢马丁)
- Agent 就像一个 Atom,但它不会阻塞,而是生成一个新线程来计算其值,仅在更改值的过程中阻塞,并且可以让其他线程知道它已完成分配。
- 引用是在事务中将自身锁定的共享位置。我们不需要让程序员决定每段锁定代码的竞争条件期间会发生什么,而是启动一个事务并让 Clojure 处理该事务中引用之间的所有锁定条件。
另外,一个相关的概念是函数 future
. 。对我来说,未来对象似乎可以被描述为同步代理,在计算完成之前根本无法访问该值。它也可以被描述为非阻塞 Atom。这些是对未来的准确构想吗?
解决方案
听起来您真的很了解 Clojure!好工作 :)
变量有一个在所有线程中可见的“根绑定”,每个单独的线程都可以更改它看到的值,而不会影响其他线程。如果我的理解是正确的,则 var 不能仅存在于一个线程中,而没有对所有人可见的根绑定,并且在使用 (def ... 定义它之前,它不能“反弹”。) 第一次。
引用在 (dosync ... 的末尾提交)包含更改的事务,但仅当事务能够以一致状态完成时。
其他提示
我认为你关于 Atoms 的结论是错误的:
原子类似于 Var,但具有线程共享安全性,在值发生更改之前会阻塞
原子改变为 swap!
或低级 compare-and-set!
. 。这永远不会阻止任何东西。 swap!
就像只有一个引用的交易一样:
- 旧值取自原子并存储在线程本地
- 该函数应用于旧值以生成新值
- 如果成功,则使用旧值和新值调用比较并设置;仅当原子的值没有被任何其他线程更改(仍然等于旧值)时,才会写入新值,否则操作将从(1)处重新开始,直到最终成功。
我发现你的问题有两个问题。
你说:
如果在操作发生时访问代理,则在操作完成之前不会返回该值
代理的状态始终可供任何线程立即读取
IE。您永远不必等待获取代理的值(我假设操作更改的值被代理并以原子方式更改)。
代码为 deref
- 方法 Agent
看起来像这样(SVN 修订版 1382):
public Object deref() throws Exception{
if(errors != null)
{
throw new Exception("Agent has errors", (Exception) RT.first(errors));
}
return state;
}
不涉及阻塞。
另外,我不明白你的意思(在你的参考部分)
事务在调用 deref 时提交
当 dosync 块的所有操作都已完成、没有抛出异常并且没有任何情况导致事务重试时,事务将被提交。我认为 deref
与此无关,但也许我误解了你的观点。
Martin 说的 Atoms 操作从 1 重新开始是正确的。直到最终成功。也称为自旋等待。虽然值得注意的是真正阻塞在锁上,但执行该操作的线程将被阻塞,直到操作成功,因此它是阻塞操作而不是异步操作。
另外关于 Futures,Clojure 1.1 添加了 Promise 和 futures 的抽象。Promise 是一种同步构造,可用于将值从一个线程传递到另一个线程。在交付值之前,任何取消引用 Promise 的尝试都将被阻止。
(def a-promise (promise))
(deliver a-promise :fred)
期货代表异步计算。它们是一种让代码在另一个线程中运行并获取结果的方法。
(def f (future (some-sexp)))
(deref f) ; blocks the thread that derefs f until value is available
变量并不总是具有根绑定。使用以下命令创建没有绑定的 var 是合法的
(def x)
或者
(declare x)
在 x 有值之前尝试评估 x 将导致
Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]