9

Let's say I do this:

(future
  (do-the-thing))

Am I guaranteed that, regardless of what (do-the-thing) does,

  • an entirely new thread will be created, rather than getting one from a pool or anything like that?
  • nothing other than (do-the-thing) will ever run on that new thread?
  • once (do-the-thing) executes on that new thread, the thread will terminate?

If not, under what circumstances would these assumptions be false?

Sam Estep
  • 12,974
  • 2
  • 37
  • 75
  • My understanding is that futures in Clojure when not using a specialized library are nothing more than Java futures. So this might have already been asked and answered as a Java question. – Chris Murphy Feb 26 '16 at 01:36
  • @ChrisMurphy If you could link me to the Java question, that would be excellent! – Sam Estep Feb 26 '16 at 01:36
  • Java futures block, whereas Scala ones don't. However this shortcoming in Clojure has been fixed with this [library](https://github.com/leonardoborges/imminent). – Chris Murphy Feb 26 '16 at 01:43
  • @ChrisMurphy I don't see how that relates to my question. Could you explain? – Sam Estep Feb 26 '16 at 01:46
  • Blocking is a big shortcoming of Java/Clojure futures, especially if not having a thread doing nothing but waiting for the future's answer is something you want to avoid. Your question was about thread usage, and 'blocking' is the 'elephant in the room' if you want to keep all available threads productive. – Chris Murphy Feb 26 '16 at 02:53
  • 1
    I think @ymonad has answered your question quite nicely. One thing I want to add is that even if it would have been implemented differently you should not rely on it for anything it's important for. AFAIK this is nowhere documented so its an implementation detail, that can always change in the future and would break any code that depends on special behaviour. If you really need a thread, then get a real Java thread. – Matthias Wimmer Feb 26 '16 at 06:46

1 Answers1

8

Short answer is No

From clojure's 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)]
...

So the executor of the future is clojure.lang.Agent/soloExecutor.

From Agent.java:

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

You can see that soloExecutor is created by Executors.newCachedThreadPool()

From document of Executors.newCachedThreadPool:

Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to execute will reuse previously constructed threads if available. If no existing thread is available, a new thread will be created and added to the pool. Threads that have not been used for sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will not consume any resources. Note that pools with similar properties but different details (for example, timeout parameters) may be created using ThreadPoolExecutor constructors.

So the answer is some other job of (do-the-thing) may be executed in same thread, and the thread will be terminated after 60 seconds if there's no more job.

You can confirm the behavior of Executors.newCachedThreadPool in following code:

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

Executing this code in clojure console, you get:

50 49 48 47 46 45 44 43 42 41 nil

for first time. And execute it again after 5 seconds, you get:

50 49 43 41 45 42 46 47 48 44 nil

Therefore you can confirm that the thread is reused.

If you execute same code after 60 seconds, you get:

60 59 58 57 56 55 54 53 52 51 nil

So you can confirm that previous threads were terminated and new threads were created.

ymonad
  • 11,710
  • 1
  • 38
  • 49
  • Just in case folks are interested: `core.async` uses `newFixedThreadPool`: http://stackoverflow.com/questions/949355/java-newcachedthreadpool-versus-newfixedthreadpool – ClojureMostly Feb 26 '16 at 15:49