未来总是创建一个新线程吗?

Sam*_*tep 5 multithreading future clojure

假设我这样做:

(future
  (do-the-thing))
Run Code Online (Sandbox Code Playgroud)

我是否可以保证,无论做什么(do-the-thing)

  • 将创建一个全新的线程,而不是从池中获取一个线程或类似的东西?
  • 除了(do-the-thing)会在那个新线程上运行之外别无他物?
  • 一旦(do-the-thing)在该新线程上执行,该线程将终止?

如果不是,在什么情况下这些假设是错误的?

ymo*_*nad 9

简短的回答是否定的

来自 clojure 的core.clj

(defmacro future
...
  [& body] `(future-call (^{:once true} fn* [] ~@body)))
...

(defn future-call 
...
  [f]
  (let [f (binding-conveyor-fn f)
        fut (.submit clojure.lang.Agent/soloExecutor ^Callable f)]
...
Run Code Online (Sandbox Code Playgroud)

所以未来的执行者是clojure.lang.Agent/soloExecutor

来自Agent.java

volatile public static ExecutorService soloExecutor = Executors.newCachedThreadPool(
    createThreadFactory("clojure-agent-send-off-pool-%d", sendOffThreadPoolCounter));
Run Code Online (Sandbox Code Playgroud)

你可以看到它soloExecutor是由Executors.newCachedThreadPool()

来自Executors.newCachedThreadPool 的文档

创建一个线程池,该线程池根据需要创建新线程,但会重用以前构造的线程(当它们可用时)。这些池通常会提高执行许多短期异步任务的程序的性能。对执行的调用将重用先前构造的线程(如果可用)。如果没有可用的现有线程,则会创建一个新线程并将其添加到池中。六十秒内未使用的线程将被终止并从缓存中删除。因此,保持空闲足够长的时间的池将不会消耗任何资源。请注意,可以使用 ThreadPoolExecutor 构造函数创建具有相似属性但不同细节(例如超时参数)的池。

所以答案是(do-the-thing)可能在同一个线程中执行其他一些作业,如果没有更多作业,该线程将在 60 秒后终止。

Executors.newCachedThreadPool您可以在以下代码中确认行为:

(doseq [i (for [x (range 10)] (future (Thread/sleep 1000) (.getId (Thread/currentThread))))] (print @i) (print " "))
Run Code Online (Sandbox Code Playgroud)

在 clojure 控制台中执行此代码,您将得到:

50 49 48 47 46 45 44 43 42 41 nil
Run Code Online (Sandbox Code Playgroud)

第一次。5秒后再次执行,得到:

50 49 43 41 45 42 46 47 48 44 nil
Run Code Online (Sandbox Code Playgroud)

因此您可以确认该线程被重用。

如果您在 60 秒后执行相同的代码,您将得到:

60 59 58 57 56 55 54 53 52 51 nil
Run Code Online (Sandbox Code Playgroud)

因此您可以确认之前的线程已终止并创建了新线程。