原帖地址:http://java.ociweb.com/mark/clojure/article.html#Concurrency
作者:R. Mark Volkmann
译者:RoySong
并发(concurrency)
维基百科上面对并发有一个精确的定义:“并发是一种系统属性,支持多条指令实时交叉运行,并且有可能会
相互影响。而交叉的指令可能会在同一CPU的不同核心中的抢占式分时线程中执行,或者在不同的物理上分离的
CPU中执行。”并发编程的主要难度在于管理可变的共享状态。
采用锁机制来管理并发是艰难的。它需要决定哪些对象需要加锁以及什么时候需要加锁,当代码产生改动或者
是添加了新代码时,这些决定又需要重新估算。如果一个开发者在对象需要加锁时忘记加锁或者没能在正确的时间
加锁,会产生不可预料的后果,比如死锁(deadlocks )或者是竞态条件(race conditions )。如果对象不需要
加锁而对它进行了加锁,则会产生性能惩罚。
支持并发是很多开发者采用Clojure的主要原因。所有数据都是不变的,除非明确采用引用类型,比如:
Var , Ref , Atom 和Agent 来标识为可变。以上这些引用类型都采用了安全的方式来管理共享状态,将在下一节“
引用类型”("Reference Types ")中讨论。
我们能够很容易在一个新线程中运行Clojure函数,包括用户自定义的有名字的或者匿名的函数。之前在“Java
交互”("Java Interoperability ")章节中已经讨论过了。
因为Clojure能够使用所有的Java类和接口,所以它也能使用所有的Java并发功能。在
"Java Concurrency In Practice "书中包含了很多这方面的例子。这本书中有很多采用Java管理并发的良好建议,
但是要遵循这些建议并不是那么简单。在大多数情况下,采用Clojure的引用类型比采用Java并发要轻松得多。
除了引用类型之外,Clojure还提供了很多函数来帮助Clojure代码在多线程中运行。
future宏能够让它内部的表达式在线程池(
CachedThreadPool
)中不同的线程中运行,
Agents 也同样采用了
线程池。这对于某些需要长时间运行而不需要即时得到返回结果的表达式是非常有用的。表达式的结果会被保存在
一个非关联对象中由future返回。如果
future宏主体还没执行完毕,而已经需要结果,当前的线程就阻塞直到返回
结果为止。当代理线程池中的某条线程使用完毕后,应该在某个点上调用shutdown-agents来使线程停止和应用程序
退出。
为了论证future的使用,在
"Defining Functions "章节末尾,一个println函数添加到derivative函数中。它帮助
确认了future啥时候被执行。注意下面代码输出结果的顺序:
(println "creating future") (def my-future (future (f-prime 2))) ; f-prime is called in another thread (println "created future") (println "result is" @my-future) (shutdown-agents)
如果f-prime函数并没有很快地结束,那么代码的输出将会是:
creating future created future derivative entered result is 9.0
pmap函数实现了并发地将某个函数应用于某个集合的每个元素。当函数应用的时间消耗超过了线程管理的日常开支时,
采用pmap能够比
map获得更好的性能表现。
clojure.parallel命名空间提供了更多的函数来帮助实现并发代码。它们包含
par
, pdistinct
, pfilter-dupes
, pfilter-nils
, pmax
, pmin
, preduce
, psort
, psummary和
pvec。