如何在Clojure中启动一个线程?

时间:2009-11-20 05:49:13

标签: multithreading concurrency clojure

我已经阅读了很多关于Clojure在并发性方面有多棒的内容,但我读过的教程中没有一个实际上解释了如何创建一个线程。你刚才做(.start(Thread.func)),还是有另外一种我错过的方式?

7 个答案:

答案 0 :(得分:39)

Clojure fnRunnable所以通常以你发布的方式使用它们,是的。

user=> (dotimes [i 10] (.start (Thread. (fn [] (println i)))))
0                                                             
1                                                             
2                                                             
4                                                             
5                                                             
3                                                             
6                                                             
7                                                             
8                                                             
9                                                             
nil

另一种选择是使用agents,在这种情况下,您将sendsend-off,并且它将使用池中的线程。

user=> (def a (agent 0))
#'user/a
user=> (dotimes [_ 10] (send a inc))
nil
;; ...later...
user=> @a
10

另一种选择是pcallspmap。还有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。