并发
Wikipedia上面对于并发有个很精准的定义:
"Concurrency is a property of systems in which several computations are executing and overlapping in time, and potentially interacting with each other. The overlapping computations may be executing on multiple cores in the same chip, preemptively time-shared threads on the same processor, or executed on physically separated processors."
并发编程的主要挑战就在于管理对于共享的、可修改的状态的修改。
通过锁来对并发进行管理是非常难的。我们需要决定哪些对象需要加锁以及什么时候加锁。这还不算完, 每次你修改代码或者添加新的代码的时候你都要重新审视下你的这些决定。如果一个开发人员忘记了去锁一个应该加锁的对象,或者锁的时机不对,一些非常糟糕的事情就会发生了。这些糟糕的事情包括 死锁 和 竞争条件 ;另一个方面如果你锁了一个不需要锁的对象,那么你的系统的性能则会下降。
为了更好地进行并发编程是很多开发人员选择Clojure的原因。Clojure的所有的数据都是只读的,除非你显示的用Var, Ref ,Atom和Agent来标明它们是可以修改的。这些提供了安全的方法去管理共享状态,我们会在下一节:“引用类型”里面更加详细地介绍。
用一个新线程来运行一个Clojure函数是非常简单的,不管它是内置的,还是自定义的, 不管它是有名的还是匿名的。关于这个更详细的可以看上面有关和Java的互操作的讨论。
因为Clojure代码可以使用java里面的所有的类和接口, 所以它可以使用Java的并发能力。Java领域一个很棒的有关java并发编程的书: " Java Concurrency In Practice ". 这本书里面讲到了很多java里面如果做好并发编程的一些建议。但是要做到这些建议并不是一件很轻松的事情。在大多数情况下,使用java的引用类型比使用java里面并发要更简单。
除了引用类型, Clojure还提供了其它一些函数来使你的并发编程更简单。
future
宏把它的body里面的表达式在另外一个线程里面执行(这个线程来自于 CachedThreadPool
,Agents(后面会介绍)用的也是这个). 这个对于那种运行时间比较长, 而且一下子也不需要运行结果的程序来说比较有用。你可以通过dereferencing 从 future
. 放回的对象来得到返回值。 如果计算已经结束了, 那么立马返回那个值;如果计算还没有结束,那么当前线程会block住,直到计算结束返回。因为这里使用了一个来自Agent线程池的线程, 所以我们要在一个适当的时机调用 shutdown-agents
关闭这些线程,然后程序才能退出。
为了演示 future
的用法, 我们加了一些println的方法调用,它能帮助我们观察方法执行的状态,注意输出的消息的顺序。
(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
函数把一个函数作用到一个集合里面的所有的元素, 和map不一样的是这个过程是完全并行的, 所以如果你要调用的这个函数是非常耗时间的话, 那么使用pmap将比使用
clojure.parallel
名字空间里买你有好多方法可以帮助你并行化你的代码, 他们包括: par
, pdistinct
, pfilter-dupes
, pfilter-nils
, pmax
, pmin
, preduce
, psort
, psummary
和 pvec
.