如何在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是使用代理 ,在这种情况下,您将sendsend-off ,它将使用池中的线程。

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

另一种select是pcallspmap 。 还有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开始的数字的当前值。

awaitfunction可以让你在代理人的活动上做一些join ,如果是长时间的话:

(await & agents)将等到他们完成了; 还有另一个版本需要超时。

是的,你在Clojure中启动一个Java线程的方式就像你在那里的一样。

然而, 真正的问题是:你为什么要这样做呢? Clojure具有比线程更好的并发结构。

如果你看看Clojure的Rich Hickey的蚁群模拟中的规范并发例子,你会发现它正好使用了0个线程。 在整个源代码中,对java.lang.Thread的唯一引用是对Thread.sleep三次调用,其唯一目的是减慢仿真速度,以便您可以真正看到 UI中发生了什么。

所有的逻辑都是在Agent中完成的:每个ant一个代理,一个animation代理和一个信息素蒸发代理。 比赛场地是一个交易参考。 不是一个线程或locking在视线内。

使用未来通常是最简单的adhoc访问线程。 完全取决于你想做什么:)

为了增加我的两分钱(7年后):Clojure函数实现了扩展CallableRunnableIFn 接口 。 因此,您可以简单地将它们传递给像Thread这样的类。

如果你的项目可能已经使用core.async ,我更喜欢使用gomacros:

 (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。