为并发而设计的 Clojure非常适合我们的Back to the Future系列 。 此外,Clojure支持开箱即用的期货。 最后但并非最不重要的一点是,Clojure是第一个在期货和promise之间进行明确区分的语言/库。 它们是如此相似,以至于大多数平台仅支持期货或将它们组合在一起。 Clojure在这里非常明确,这很好。 让我们从诺言开始:
承诺
Promise是一个封装不可变值的线程安全对象。 此值可能尚不可用,以后可以从任何线程准确地传递一次。 如果其他线程尝试在交付之前取消对诺言的引用 ,它将无限阻塞。 如果承诺
已解决(已交付),不会发生阻塞。 承诺只能交付一次,并且一旦设置就永远不能更改其价值:
(def answer (promise))
@answer
(deliver answer 42)
answer
是promise
。 尝试使用@answer
或(deref answer)
对此进行解除引用将会很简单。 该线程或其他线程必须首先为该承诺提供一些价值(使用deliver
函数)。 在deref
上阻塞的所有线程deref
将唤醒,随后取消引用此诺言的尝试将立即返回42
。 Promise是线程安全的,以后不能修改。 试图传递另一个值来answer
被忽略了。
期货
从用户的角度来看,期货在Clojure中的行为几乎相同-它们是单个值的容器(当然,它可以是map
或list
-但应该是不可变的),并尝试在解析无限块之前取消引用。 就像承诺一样,期货只能被解决一次,而取消引用解决的未来具有立竿见影的效果。 两者之间的区别在于语义,而不是技术。 Future表示后台计算,通常在线程池中,而promise只是一个简单的容器,任何人都可以在任何时间点进行交付(填充)。 通常,没有关联的后台处理或计算。 这更像是我们正在等待的事件(例如,我们正在等待的JMS消息回复 )。 话虽如此,让我们开始一些异步处理。 与Akka相似 ,底层线程池是隐式的,我们只需传递要在后台运行的代码。 例如,要计算低于一千万的正整数之和,我们可以说:
(let
[sum (future (apply + (range 1e7)))]
(println "Started...")
(println "Done: " @sum)
)
sum
是将来的实例。 在后台线程中开始计算时,将立即显示"Started..."
消息。 但@sum
阻止,我们实际上不得不等待一点点1看到"Done: "
的消息和计算结果。 这里是其中最令人失望到达:没有future
,也没有promise
Clojure中不支持收听完成/失败异步。 该API等效于非常有限的java.util.concurrent.Future<T>
。 我们可以创建future
, cancel
它 ,检查它是否realized?
(已解决)并阻止其等待值。 就像Java中的Future<T>
一样,事实上future
函数的结果甚至实现了java.util.concurrent.Future<T>
。 尽管我非常喜欢STM和代理之类的Clojure并发原语,但期货仍然有些欠发达。 缺少事件驱动的异步回调,它们在期货完成时会被调用(请注意, add-watch
无法使用期货,并且仍处于alpha状态)大大降低了期货对象的有用性。 我们不能再:
很遗憾,由于这不是技术难题,而只是缺少的API,我希望很快能看到对完成侦听器的支持。 为了完整起见,这里有一个更大的程序,它使用期货同时获取多个网站的内容,这是我们的网络爬网示例的基础:
(let [
top-sites `("www.google.com" "www.youtube.com" "www.yahoo.com" "www.msn.com")
futures-list (doall (
map #(
future (slurp (str "http://" %))
)
top-sites
))
contents (map deref futures-list)
]
(doseq [s contents] (println s))
)
上面的代码开始同时下载多个网站的内容。 map deref
等待所有结果,并且一旦来自futures-list
所有doseq
全部完成, doseq
打印内容( contents
是字符串列表)。
我最初陷入的陷阱是缺少doall
(这会强制执行延迟序列评估)。 map
会在top-sites
列表之外产生延迟序列,这意味着只有在首次访问futures-list
给定项时才调用future
函数。 那很好。 但是每个项目仅在(map deref futures-list)
期间首次访问。 这意味着在等待第一个将来被取消引用的同时,第二个未来甚至还没有开始! 它从第一个将来完成时开始,我们尝试取消引用第二个。 这意味着当所有先前的期货都已经完成时,最后的期货开始。 为了削减长话短说,不doall
是强制所有期货立即开始,我们的代码按顺序运行,一个又一个的未来。 副作用之美。
1 – Scala中的BTW (1L to 9999999L).sum
快了几乎一个数量级, 只是说 ...
参考: NoBlogDefFound博客中来自我们JCG合作伙伴 Tomasz Nurkiewicz的Clojure承诺和期货 。
翻译自: https://www.javacodegeeks.com/2013/03/promises-and-futures-in-clojure.html