28

我正在尝试从网站上提供的 API 和文档中学习 Clojure。我对 Clojure 中的可变存储有点不清楚,我想确保我的理解是正确的。如果有任何想法我错了,请告诉我。

编辑:当我收到关于其正确性的评论时,我正在更新它。


免责声明:所有这些信息都是非正式的并且可能是错误的。不要使用这篇文章来了解 Clojure 的工作原理。


Vars总是包含一个根绑定,并且可能包含一个每个线程的绑定。它们与命令式语言中的常规变量相当,不适合在线程之间共享信息。(感谢亚瑟乌尔费尔特)

Refs是在支持原子事务的线程之间共享的位置,这些原子事务可以更改单个事务中任意数量的 refs 的状态。在退出同步表达式(dosync)时提交事务,并使用 STM 魔法(回滚、队列、等待等)自动解决冲突

代理是通过调度独立的动作函数来改变代理的状态,使信息能够以最小的开销在线程之间异步共享的位置。代理会立即返回,因此是非阻塞的,尽管在分派函数完成之前不会设置代理的值。

原子是可以在线程之间同步共享的位置。它们支持不同线程之间的安全操作。

这是我基于何时使用这些结构的友好总结:

  • Vars 就像命令式语言中的常规旧变量。(尽可能避免)
  • Atom 类似于 Vars,但具有线程共享安全性,允许立即读取和安全设置。(感谢马丁)
  • 代理就像一个原子,但它不会阻塞它会产生一个新线程来计算它的值,只有在更改值的过程中才会阻塞,并且可以让其他线程知道它已完成分配。
  • Refs 是在事务中锁定自己的共享位置。我们不需要让程序员决定在每段锁定代码的竞争条件下会发生什么,我们只需启动一个事务并让 Clojure 处理该事务中 ref 之间的所有锁定条件。

另外,一个相关的概念是函数future。对我来说,future 对象似乎可以被描述为一个同步代理,其中在计算完成之前根本无法访问该值。它也可以被描述为一个非阻塞的 Atom。这些是对未来的准确概念吗?

4

5 回答 5

6

听起来你真的在使用 Clojure!做得好 :)

Vars 在所有线程中都有一个可见的“根绑定”,每个单独的线程都可以更改它看到的值,而不会影响其他线程。如果我的理解是正确的,那么一个 var 不能只存在于一个线程中而没有一个对所有人可见的根绑定,并且在第一次使用 (def ... ) 定义它之前它不能“反弹”。

Refs 在包含更改的 (dosync ... ) 事务结束时提交,但仅在事务能够以一致状态完成时提交。

于 2009-06-22T17:13:46.423 回答
4

我认为您关于 Atoms 的结论是错误的:

Atom 类似于 Vars,但具有线程共享安全性,在值更改之前会阻塞

原子用swap!或低级改变compare-and-set!。这永远不会阻止任何东西。swap!就像只有一个参考的交易:

  1. 旧值取自原子并存储在线程本地
  2. 该函数应用于旧值以生成新值
  3. 如果成功,则使用旧值和新值调用 compare-and-set;只有当原子的值没有被任何其他线程更改(仍然等于旧值)时,才写入新值,否则操作在 (1) 处重新开始,直到最终成功。
于 2009-06-24T09:04:09.033 回答
3

我发现你的问题有两个问题。

你说:

如果在操作发生时访问代理,则在操作完成之前不会返回该值

http://clojure.org/agents说:

代理的状态总是可以立即被任何线程读取

即,您永远不必等待获取代理的值(我假设由操作更改的值是代理并以原子方式更改)。

derefan的 - 方法的代码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与它无关,但也许我误解了你的意思。

于 2009-06-22T20:57:23.673 回答
1

Martin 说 Atoms 操作在 1 重新开始时是对的。直到最终成功。它也称为旋转等待。虽然注意确实阻塞了锁,但执行操作的线程被阻塞,直到操作成功,所以它是阻塞操作而不是异步操作。

同样关于 Futures,Clojure 1.1 增加了对 Promise 和 Futures 的抽象。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
于 2010-02-22T23:20:19.160 回答
0

Vars 并不总是具有根绑定。创建一个没有绑定的 var 是合法的

(def x)

或者

(declare x)

尝试在 x 有值之前对其进行评估将导致

Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]
于 2010-09-18T22:00:24.500 回答