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

47

我读了很多有关Clojure在并发方面非常出色的文章,但是我读过的所有教程都没有解释如何创建线程。您只需执行(.start (Thread. func)),还是我错过了其他方法?

7个回答

46

Clojure的fn函数是可运行的,因此通常可以像您发布的方式一样使用它们。

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
2
不要忘记像pmap这样的函数! - user21037
Clojure函数是可运行的,因此通常可以像您发布的方式一样使用它们。非常感谢您提供这些信息。 - sjas
小修正:send 将使用线程池中的一个线程。send-off 将使用一个独立的线程。这就是它们之间的区别。如果您的操作会阻塞,请使用 send-off(否则您可能会阻塞代理池中的所有线程)。 - Blake Miller

40

通常在Clojure中启动线程时,我只是使用future

除了使用简单外,这种方法的优点是避免了访问底层Java线程机制时需要进行任何混乱的Java互操作。

示例用法:

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

这将在另一个线程中异步执行该函数。

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

如果你想获取结果,只需取消引用future对象,例如:

@a
=> 100
请注意,@a会一直阻塞,直到未来线程完成其工作。

2
还要注意的是,直到您取消引用 future,异常才会被抛出。请参见 https://stuartsierra.com/2015/05/27/clojure-uncaught-exceptions - jwhitlark

14

Programming 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)将等待直到它们都完成;还有另一个版本,它需要一个超时参数。


8

是的,在Clojure中启动Java线程的方式与您所示的方式类似。

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

如果您查看Clojure中的典型并发示例Rich Hickey's ant colony simulation,您会发现它完全不使用线程。在整个源代码中,对java.lang.Thread的唯一引用是三次调用Thread.sleep,其唯一目的是减缓模拟速度,以便您实际上可以看到UI中正在发生的事情。

所有逻辑都在代理中完成:每只蚂蚁一个代理,动画一个代理,信息素蒸发一个代理。游戏场地是一个事务性ref。没有线程或锁可见。


10
这不正确。Clojure在底层当然使用线程,但它提供了抽象来同步和协调这些线程,例如futures、pmap或agents。此外,有些问题需要使用java.util.concurrent机制来使用线程。 - kotarak
5
我会给Jörg一个怀疑的余地,即当他说“threads”时,他的意思是“Thread API”,但这应该明确表达。 - Mike Douglas
9
Clojure使用哪些内部运行时机制来实现代理、未来等等,那是Rich Hickey的事情,不是我的。今天,Clojure的JVM版本恰巧使用线程池完全与语言语义无关。明天Rich可能会改变主意,将它们实现为Continuations,.NET版本的Clojure可能将它们实现为“Task”,ClojureScript(Clojure的JavaScript版本)可能将它们实现为HTML5 Web Workers,而一个假设中的未来的Erlang托管版本可能将它们实现为Actors。作为Clojure用户,我永远不会知道这些。 - Jörg W Mittag
5
不正确。如果任务足够小,pmap 的性能会低于 map。抽象是有漏洞的,你应该始终了解底层发生了什么。 - Mike Douglas
3
这段话的大意是:视你对“leaky”的定义而定 :) 在理想(即完全抽象化)的世界中,同时执行操作比按顺序执行操作更慢似乎是不可能的。正确应用pmap需要理解生成和协调线程的成本,所以你已经逃离了“理想并行应用”的世界。这就是我所说的“leaky”的含义;这并不是对语言的批评。正如Brian提到的,这通常是一种可取的方面。 - Mike Douglas
显示剩余2条评论

8

仅供参考(7年后):Clojure函数实现了IFn接口,该接口扩展了CallableRunnable。因此,您可以将它们简单地传递给Thread等类。

如果您的项目可能已经使用core.async,我建议使用go宏:

(go func)

这将在一个超轻量级的IOC(控制反转)线程中执行func

go [...]将把主体变成状态机。 在达到任何阻塞操作时,状态机将被“停放”,实际的控制线程将被释放。 [...]当阻塞操作完成时,代码将会恢复[...]

如果func将要执行I/O或一些长时间运行的任务,则应使用thread,它也是core.async的一部分(请查看this出色的博客文章):
(thread func)

无论如何,如果您想坚持使用Java互操作语法,请考虑使用->(线程/箭头)宏:
(-> (Thread. func) .start)

2

使用future通常是最简单的临时线程访问方式。完全取决于您想要做什么 :)


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接