2009-11-20 309 views

回答

37

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,在这种情况下,你会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

啊,是的,代理机制是我忘记的东西。谢谢! – andrewdotnich 2009-11-20 14:52:44

+2

不要忘了pmap! – Dan 2009-11-26 13:29:23

+0

“Clojure fns是可运行的,所以通常按照您发布的方式使用它们,是的。”非常感谢您提供这些信息。 – sjas 2013-12-29 19:31:28

7

是的,您在Clojure中启动Java线程的方式就像您在那里拥有的东西。

但是,真实的问题是:你为什么要这么做? Clojure拥有比线程更好的并发结构。

如果您看一下Clojure中的规范并发示例,Rich Hickey's ant colony simulation,您将看到它恰好使用0个线程。整个数据源中对java.lang.Thread的唯一引用是对Thread.sleep的三次调用,其唯一目的是减慢模拟速度,以便您实际上可以请参阅 UI中正在进行的操作。

所有的逻辑都在代理中完成:每个蚂蚁一个代理,一个动画代理和一个信息素蒸发代理。比赛场地是交易参考。不是一个线程或锁定在视线内。

+8

这是不正确的。 Clojure使用当然线程下的线程。但它提供了同步和协调这些线程的抽象,例如。期货,pmap或代理商。然后出现问题,你想用java.util.concurrent机制来使用线程。 – kotarak 2009-11-20 06:39:09

+4

我想给Jörg带来这样的疑问:当他说线程时,他的意思是线程API,但这应该清楚。 – 2009-11-20 06:47:59

+9

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

14

编程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)将等待,直到他们全部完成;还有另一个版本需要超时。

2

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

30

通常,当我想在Clojure中启动一个线程时,我只需使用future

除了使用简单之外,这样做的好处是可以避免执行任何繁琐的Java互操作来访问底层的Java线程机制。

实例:

(future (some-long-running-function)) 

这将在另一个线程异步执行该功能。

(def a (future (* 10 10))) 

如果你想要得到的结果,只是取消引用的将来,e.g:

@a 
=> 100 

注意@a将阻塞,直到未来的线程已完成其工作。

+0

太美了:'3 – d9k 2014-10-06 12:16:11

+0

另外请注意,除非您对未来有所反应,否则不会抛出异常。请参阅https:// stuartsierra。COM/2015/5月27日/ Clojure中,未捕获的例外 – jwhitlark 2017-09-28 19:04:15

0

(future f)宏在Callable(通过fn *)封装了表单f并将其立即提交给线程池。

,如果你需要一个java.lang.Thread中的对象的引用,例如,使用它作为一个java.lang.Runtime中的关闭挂钩,您可以创建这样一个主题:

(proxy [Thread] [] (run [] (println "running"))) 

这不会启动线程,只创建它。 创建和运行线程,将其提交给一个线程池,或致电。开始它:

(-> 
(proxy [Thread] [] (run [] (println "running"))) 
(.start)) 

Brians的回答也创建一个线程,但并不需要代理,所以这是非常优雅。另一方面,通过使用代理,我们可以避免创建一个Callable。

2

只需添加我的两美分(7年后):Clojure功能实现IFninterface,延伸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) 
相关问题