如何在Clojure中启动一个线程?
关于并发性,我已经读了很多关于Clojure的优秀之处,但是我没有看到实际的教程解释了如何创build一个线程。 你只是(。开始(Thread。func)),还是有其他的方式,我已经错过了?
Clojure是可运行的,所以通常按照你发布的方式使用它们。
user=> (dotimes [i 10] (.start (Thread. (fn [] (println i))))) 0 1 2 4 5 3 6 7 8 9 nil
另一个select是使用代理 ,在这种情况下,您将send
或send-off
,它将使用池中的线程。
user=> (def a (agent 0)) #'user/a user=> (dotimes [_ 10] (send a inc)) nil ;; ...later... user=> @a 10
另一种select是pcalls
和pmap
。 还有future
。 它们都logging在Clojure API中 。
通常当我想在Clojure中启动一个线程时,我只是使用未来 。
除了使用起来简单之外,还有一个好处,就是避免了为了访问底层Java线程机制而进行繁杂的Java互操作。
用法示例:
(future (some-long-running-function))
这将在另一个线程中asynchronous执行该函数。
(def a (future (* 10 10)))
如果你想得到的结果,只是取消未来,例如:
@a => 100
请注意,@a将阻塞,直到将来的线程完成其工作。
编程Clojure没有解决这个问题,直到第167页:“使用代理进行asynchronous更新”。
在你开始线程之前,请注意,Clojure将自己的多任务,给一半的机会。 我已经写了一些对并发性无知的程序,并发现当条件合适时,它们会占用多个CPU。 我知道这不是一个非常严格的定义:我还没有深入探讨这一点。
但是对于那些确实需要明确的单独活动的场合,Clojure的答案显然是代理人。
(agent initial-state)
将创build一个。 它不像是一个等待被执行的代码块的Java线程。 相反,这是一个等待被赋予工作的活动。 你通过这样做
(send agent update-fn & args)
这个例子呢
(def counter (agent 0))
counter
是你的名字和代理人的处理; 代理的状态是数字0。
设置完毕后,您可以将工作发送给代理:
(send counter inc)
将告诉它将给定的函数应用于其状态。
稍后,您可以通过取消引用将状态从代理程序中提取出来:
@counter
会给你从0开始的数字的当前值。
await
function可以让你在代理人的活动上做一些join
,如果是长时间的话:
(await & agents)
将等到他们完成了; 还有另一个版本需要超时。
是的,你在Clojure中启动一个Java线程的方式就像你在那里的一样。
然而, 真正的问题是:你为什么要这样做呢? Clojure具有比线程更好的并发结构。
如果你看看Clojure的Rich Hickey的蚁群模拟中的规范并发例子,你会发现它正好使用了0个线程。 在整个源代码中,对java.lang.Thread
的唯一引用是对Thread.sleep
三次调用,其唯一目的是减慢仿真速度,以便您可以真正看到 UI中发生了什么。
所有的逻辑都是在Agent中完成的:每个ant一个代理,一个animation代理和一个信息素蒸发代理。 比赛场地是一个交易参考。 不是一个线程或locking在视线内。
使用未来通常是最简单的adhoc访问线程。 完全取决于你想做什么:)
为了增加我的两分钱(7年后):Clojure函数实现了扩展Callable
和Runnable
的IFn
接口 。 因此,您可以简单地将它们传递给像Thread
这样的类。
如果你的项目可能已经使用core.async ,我更喜欢使用go
macros:
(go func)
这在一个超级轻量级IOC(反转控制)线程中执行func
:
去 […]将身体变成一个状态机。 在达到任何阻塞操作时,状态机将被“停放”,实际的控制线程将被释放。 […]阻塞操作完成后,代码将恢复[…]
如果func
要做I / O或者一些长时间运行的任务,你应该使用也是core.async的一部分的线程(查看这个优秀的博客文章):
(thread func)
无论如何,如果你想坚持Java互操作语法,可以考虑使用->
(thread / arrow)macros:
(-> (Thread. func) .start)
(future f)
macros在Callable(通过fn *)封装表单f,并立即提交给线程池。
如果您需要对java.lang.Thread对象的引用,例如,将其用作java.lang.Runtimeclosures挂钩,则可以像这样创build一个Thread:
(proxy [Thread] [] (run [] (println "running")))
这不会启动线程,只创build它。 要创build并运行线程,请将其提交到线程池或调用.start:
(-> (proxy [Thread] [] (run [] (println "running"))) (.start))
Brians的答案也创build一个线程,但不需要代理,所以这是非常优雅的。 另一方面,通过使用代理,我们可以避免创build一个Callable。