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

and*_*ich 42 concurrency multithreading clojure

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

Bri*_*per 39

Clojure fnRunnable如此通常以你发布的方式使用它们,是的.

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

另一个选择是使用代理,在这种情况下你会send或者send-off它将使用池中的线程.

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

另一种选择是pcallspmap.还有future.它们都记录在Clojure API中.

  • 别忘了像pmap这样的人! (2认同)

mik*_*era 32

通常,当我想在Clojure中启动一个线程时,我只是使用未来.

除了易于使用之外,这还有一个优点,即您可以避免使用任何混乱的Java互操作来访问底层的Java线程机制.

用法示例:

(future (some-long-running-function))
Run Code Online (Sandbox Code Playgroud)

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

(def a (future (* 10 10)))
Run Code Online (Sandbox Code Playgroud)

如果您想获得结果,只需取消引用未来,例如:

@a
=> 100
Run Code Online (Sandbox Code Playgroud)

请注意,@ a将阻塞,直到将来的线程完成其工作.

  • 另请注意,除非您取消引用未来,否则不会抛出异常。见 https://stuartsierra.com/2015/05/27/clojure-uncaught-exceptions (2认同)

Car*_*icz 14

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


Jör*_*tag 9

是的,你在Clojure中启动Java Thread的方式就像你在那里一样.

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

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

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

  • Clojure在其内部运行时使用什么来实现代理,期货等什么是Rich Hickey的业务,而不是我的业务.今天Clojure的JVM版本恰好使用线程池这一事实与语言语义完全无关.明天Rich可能会改变主意并将其作为Continuations实现,Clojure的.NET版本可能将它们实现为`Task`s,ClojureScript(Clojure的JavaScript版本)可能将它们实现为HTML5 Web Workers,以及假设的未来Erlang托管版本可能会将它们实现为Actors.作为Clojure用户,我永远不会知道. (9认同)
  • 这是不正确的.Clojure使用引擎盖下的线程.但它提供了同步和协调这些线程的抽象,例如.期货,pmap或代理商.然后有一些问题,你想在java.util.concurrent机器中使用线程. (8认同)
  • 不对.如果任务足够小,`pmap`将落后于`map`.抽象是漏洞,您应该始终了解下面发生的事情. (5认同)
  • 我给Jörg带来的疑问是,当他说线程时他意味着线程API,但这应该是明确的. (4认同)
  • Clojure经常故意泄漏.它暴露了Java,因此您可以利用它.(Clojure strings == Java strings etc.)Clojure不会很快破坏与普通Java Threads的兼容性,这是我的猜测. (3认同)
  • @Surya取决于你对漏洞的定义:)在理想化(即完全抽象化)的世界中,并行执行动作的速度比依次执行的动作慢似乎是不可能的.正确应用`pmap`需要你了解产生和协调线程的成本,所以你已经逃脱了"理想并行应用程序"的世界.这就是我所说的泄漏; 它并不是对语言的批评.正如布莱恩所说,它通常是一个理想的方面. (2认同)

bea*_*u13 6

再加上我的两分钱(7 年后):Clojure 函数实现了扩展的IFn 接口Callable以及Runnable. 因此,您可以简单地将它们传递给像Thread.

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

(go func)
Run Code Online (Sandbox Code Playgroud)

func在超轻量级IOC(控制反转)线程中执行

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

如果func要执行 I/O 或一些长时间运行的任务,您应该使用thread它也是 core.async 的一部分(查看这篇优秀的博客文章):

(thread func)
Run Code Online (Sandbox Code Playgroud)

无论如何,如果您想坚持 Java 互操作语法,请考虑使用->(thread/arrow) 宏:

(-> (Thread. func) .start)
Run Code Online (Sandbox Code Playgroud)