如何在Clojure中启动一个线程?
我读过很多关于Clojure在并发方面的优秀之处,但是我没有阅读过的教程实际上解释了如何创建线程。你只是(.start(Thread。func)),还是有另一种我错过的方式?如何在Clojure中启动一个线程?
Clojure fn
s是Runnable
因此,通常按照您发布的方式使用它们,是的。
user=> (dotimes [i 10] (.start (Thread. (fn [] (println i)))))
0
1
2
4
5
3
6
7
8
9
nil
另一种选择是使用agents,在这种情况下,你会send
或send-off
,它会使用一个线程池。
user=> (def a (agent 0))
#'user/a
user=> (dotimes [_ 10] (send a inc))
nil
;; ...later...
user=> @a
10
另一种选择是pcalls
和pmap
。还有future
。他们都记录在Clojure API。
是的,您在Clojure中启动Java线程的方式就像您在那里拥有的东西。
但是,真实的问题是:你为什么要这么做? Clojure拥有多比线程更好的并发结构。
如果您看一下Clojure中的规范并发示例,Rich Hickey's ant colony simulation,您将看到它恰好使用0个线程。整个数据源中对java.lang.Thread
的唯一引用是对Thread.sleep
的三次调用,其唯一目的是减慢模拟速度,以便您实际上可以请参阅 UI中正在进行的操作。
所有的逻辑都在代理中完成:每个蚂蚁一个代理,一个动画代理和一个信息素蒸发代理。比赛场地是交易参考。不是一个线程或锁定在视线内。
这是不正确的。 Clojure使用当然线程下的线程。但它提供了同步和协调这些线程的抽象,例如。期货,pmap或代理商。然后出现问题,你想用java.util.concurrent机制来使用线程。 – kotarak 2009-11-20 06:39:09
我想给Jörg带来这样的疑问:当他说线程时,他的意思是线程API,但这应该清楚。 – 2009-11-20 06:47:59
Clojure在其内部运行时使用什么来实现代理,期货以及Rich Hickey的业务,而不是我的业务。事实上,Clojure的JVM版本恰好使用了线程池,这与语言语义完全无关。 Tomorrow Rich可能会改变主意并将它们实现为Continuations,Clojure的.NET版本可能会将它们实现为“任务”,ClojureScript(Clojure的JavaScript版本)可能将它们实现为HTML5 Web Workers,并假设未来Erlang托管版本可能将它们实现为Actor。作为Clojure用户,我永远不会知道。 – 2009-11-20 07:08:20
编程Clojure直到页167:“使用代理进行异步更新”才解决该问题。
在你开始线程之前,请注意Clojure将自己完成多任务,只要有一半机会。我已经写出了对并发性无知的程序,并发现当条件合适时,它们占用多个CPU。我知道这不是一个非常严格的定义:我还没有深入探讨这一点。
但是对于那些你确实需要明确的单独活动的场合,Clojure的答案之一显然是代理人。
(agent initial-state)
将创建一个。它不像是一个等待执行的代码块的Java线程。相反,这是一项等待开展工作的活动。你这样做通过
(send agent update-fn & args)
的例子并
(def counter (agent 0))
counter
是你的名字,并办理代理;代理的状态是具有设置了数字0
,你可以把工作交给代理人:
(send counter inc)
会告诉它给定函数应用到它的状态。
以后,您可以通过取消引用它拉出来的状态的代理:
@counter
会给你,在0开始走出数的当前值。
功能await
会让你这样做对代理的活动join
,它应该是一个漫长的:
(await & agents)
将等待,直到他们全部完成;还有另一个版本需要超时。
使用未来通常是最简单的adhoc访问线程。完全取决于你想要做什么:)
通常,当我想在Clojure中启动一个线程时,我只需使用future。
除了使用简单之外,这样做的好处是可以避免执行任何繁琐的Java互操作来访问底层的Java线程机制。
实例:
(future (some-long-running-function))
这将在另一个线程异步执行该功能。
(def a (future (* 10 10)))
如果你想要得到的结果,只是取消引用的将来,e.g:
@a
=> 100
注意@a将阻塞,直到未来的线程已完成其工作。
(future f)
宏在Callable(通过fn *)封装了表单f并将其立即提交给线程池。
,如果你需要一个java.lang.Thread中的对象的引用,例如,使用它作为一个java.lang.Runtime中的关闭挂钩,您可以创建这样一个主题:
(proxy [Thread] [] (run [] (println "running")))
这不会启动线程,只创建它。 创建和运行线程,将其提交给一个线程池,或致电。开始它:
(->
(proxy [Thread] [] (run [] (println "running")))
(.start))
Brians的回答也创建一个线程,但并不需要代理,所以这是非常优雅。另一方面,通过使用代理,我们可以避免创建一个Callable。
只需添加我的两美分(7年后):Clojure功能实现IFn
interface,延伸Callable
以及Runnable
。因此,您可以简单地将它们传递给像Thread
这样的类。
如果你的项目可能已经使用core.async,我喜欢使用go
宏:
(go func)
这在执行func
超轻IOC (inversion of control) thread:
去 [...]会把身体变成一个状态机。在达到任何阻塞操作时,状态机将被“停放”并且实际的控制线程将被释放。 [...]当阻塞操作完成后,该代码将恢复[...]
如果func
会做I/O或需要长时间运行的任务,你应该使用thread
这也是core.async的部分(检查this优秀博客文章):
(thread func)
无论如何,如果你想坚持到Java互操作的语法,请考虑使用->
(线程/箭头)宏:
(-> (Thread. func) .start)
啊,是的,代理机制是我忘记的东西。谢谢! – andrewdotnich 2009-11-20 14:52:44
不要忘了pmap! – Dan 2009-11-26 13:29:23
“Clojure fns是可运行的,所以通常按照您发布的方式使用它们,是的。”非常感谢您提供这些信息。 – sjas 2013-12-29 19:31:28