我已经阅读了很多关于Clojure在并发性方面有多棒的内容,但我读过的教程中没有一个实际上解释了如何创建一个线程。你刚才做(.start(Thread.func)),还是有另外一种我错过的方式?
答案 0 :(得分:39)
Clojure fn
是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。
答案 1 :(得分:32)
通常当我想在Clojure中启动一个线程时,我只使用future。
除了易于使用之外,还有一个优点,即您可以避免使用任何混乱的Java互操作来访问底层的Java线程机制。
使用示例:
(future (some-long-running-function))
这将在另一个线程中异步执行该函数。
(def a (future (* 10 10)))
如果你想得到结果,只需取消引用未来,例如:
@a
=> 100
请注意,@ a将阻止,直到将来的线程完成其工作。
答案 2 :(得分:15)
编程Clojure 直到第167页“使用代理进行异步更新”才解决该问题。
在你开始创建线程之前,请注意Clojure会自己多次任务,只有一半机会。我编写的程序对并发性一无所知,发现当条件合适时,它们占用的CPU不止一个。我知道这不是一个非常严格的定义:我还没有深入探讨过这个问题。
但是对于那些你确实需要一个明确的单独活动的场合,Clojure的一个答案显然是代理人。
(agent initial-state)
将创建一个。就等待执行的代码块而言,它不像Java Thread。相反,这是一项等待工作的活动。你这样做是通过
(send agent update-fn & args)
示例
(def counter (agent 0))
counter
是你的代理人的名字和手柄;代理人的状态是数字0。
设置完成后,您可以将工作发送给代理:
(send counter inc)
将告诉它将给定的函数应用于其状态。
稍后您可以通过取消引用来将状态从代理中拉出来:
@counter
将为您提供从0开始的数字的当前值。
功能await
可让您对代理商的活动执行类似join
的操作,如果它很长,则可以:
(await & agents)
将等到他们全部完成;还有另一个需要暂停的版本。
答案 3 :(得分:9)
是的,在Clojure中启动Java Thread的方式就像你在那里一样。
然而,真正的问题是:你为什么要这样做? Clojure比线程具有更多更多更好的并发构造。
如果你看一下Clojure中的规范并发示例Rich Hickey's ant colony simulation,你会看到它正好使用了0个线程。在整个来源中对java.lang.Thread
的唯一引用是对Thread.sleep
的三次调用,其唯一目的是减慢模拟速度,以便您实际上可以查看正在进行的操作用户界面。
所有逻辑都在Agent中完成:每个蚂蚁一个代理,一个动画代理和一个信息素蒸发代理。比赛场地是交易参考。不是线索也不锁定。
答案 4 :(得分:4)
只需添加我的两分钱(7年后):Clojure函数实现IFn
interface扩展Callable
以及Runnable
。因此,您只需将它们传递给Thread
等类。
如果您的项目可能已使用core.async,我更喜欢使用go
宏:
(go func)
这会在超级轻量级IOC (inversion of control) thread中执行func
:
go [...]将身体变成状态机。在达到任何阻止操作后,状态机将被停放在'并将释放实际的控制线程。 [...]当阻塞操作完成时,代码将恢复[...]
如果func
要执行I / O或一些长时间运行的任务,您应该使用thread
,这也是core.async的一部分(请查看this优秀的博文):
(thread func)
无论如何,如果您想坚持使用Java互操作语法,请考虑使用->
(线程/箭头)宏:
(-> (Thread. func) .start)
答案 5 :(得分:2)
使用未来通常是最简单的线程访问权限。完全取决于你想做什么:)
答案 6 :(得分:0)
(future f)
宏将表单f包装在Callable(通过fn *)中,并立即将其提交给线程池。
如果你需要对java.lang.Thread对象的引用,例如,将它用作java.lang.Runtime关闭钩子,你可以像这样创建一个Thread:
(proxy [Thread] [] (run [] (println "running")))
这不会启动线程,只创建它。 要创建并运行该线程,请将其提交给线程池或在其上调用.start:
(->
(proxy [Thread] [] (run [] (println "running")))
(.start))
Brians的回答也创造了一个线索,但不需要代理,因此非常优雅。另一方面,通过使用代理,我们可以避免创建Callable。