When running a test on futures I found:
user=> (time (doall (map deref (for [i (range 1000)]
#_=> (future (Thread/sleep 1000))))))
"Elapsed time: 32058.208 msecs"
When powers of 2 are visible there are alarmbells ringing in my head. This smells like only 32 threads are started.
Some targetted experiments:
user=> (time (doall (map deref (for [i (range 32)]
#_=> (future (Thread/sleep 1000))))))
"Elapsed time: 1002.302 msecs"
(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)
user=> (time (doall (map deref (for [i (range 64)]
#_=> (future (Thread/sleep 1000))))))
"Elapsed time: 2004.24 msecs"
(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)
user=> (time (doall (map deref (for [i (range 65)]
#_=> (future (Thread/sleep 1000))))))
"Elapsed time: 3005.279 msecs"
(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)
confirm this. We see an additional second for each slice of 32 future tasks.
The code to make a future is
(defmacro future
"Takes a body of expressions and yields a future object that will
invoke the body in another thread, and will cache the result and
return it on all subsequent calls to deref/@. If the computation has
not yet finished, calls to deref/@ will block, unless the variant of
deref with timeout is used. See also - realized?."
{:added "1.1"}
[& body] `(future-call (^{:once true} fn* [] ~@body)))
the interesting bit in future-call is
fut (.submit clojure.lang.Agent/soloExecutor ^Callable f)]
which translates to:
volatile public static ExecutorService soloExecutor = Executors.newCachedThreadPool(
createThreadFactory("clojure-agent-send-off-pool-%d", sendOffThreadPoolCounter));
So this creates indeed an unbounded threadpool.
So why are only 32 threads running instead of creating 1000 threads and returning in a second?
This
(time (doall (map deref (for [i (range 65)] (future (Thread/sleep 1000))))))
; "Elapsed time: 3005.244983 msecs"
should actually take 65 seconds because you are asking to deref the futures in sequence on the main thread. These futures have not even started when the deref comes because for
is lazy. The reason you get multiples of 32 is the chunking behavior of range
.
Compare to non-chunked version
(time (doall (map deref (for [i (apply list (range 65))] (future (Thread/sleep 1000))))))
; "Elapsed time: 64997.260808 msecs"
As pointed out by Rörd in the comments, adding another doall
to remove the laziness of the future creation solves the problem.
(time (doall (map deref (doall (for [i (range 65)] (future (Thread/sleep 1000)))))))
; "Elapsed time: 999.263631 msecs"
Another way to test completion of all the futures is to wait on a promise that is not delivered until the last future completes.
(defn test-futures [n]
(let [a (atom 0)
p (promise)
f (fn [] (swap! a inc) (when (= @a n) (deliver p n)))]
(doseq [i (range n)] (future (do (Thread/sleep 1000) (f))))
(deref p)))
And now you can see that completion of 64, 65, or 1000 of these futures occurs each in about 1 second.
(time (test-futures 64))
; "Elapsed time: 996.262272 msecs"
; 64
(time (test-futures 65))
; "Elapsed time: 996.554436 msecs"
; 65
(time (test-futures 1000))
; "Elapsed time: 1000.247374 msecs"
; 1000
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With