Rob N
Rob N

Reputation: 16439

How to execute some Clojure futures in a single thread?

I'd like to create some futures in Clojure and run them all on a specific thread, to make sure they run one-at-a-time. Is this possible?

It's not hard to wrap the Java libraries to do this, but before I do that I want to make sure I'm not missing a Clojure way of doing it. In Java I can do this by implementing FutureTask and submitting those tasks to a single-threaded executor.

Upvotes: 1

Views: 1132

Answers (4)

l0st3d
l0st3d

Reputation: 2968

In addition the promises mentioned, you can use a delay. Promises have the problem that you can accidentally not deliver them, and create a deadlock scenario that's not possible with futures and delays. The difference between a future and a delay is only the thread that the work is executed on. With a future, the work is done in the background, and with a delay the work is done by the first thread that tries to deref it. So if future's are a better fit than promises, you could always do something like:

(def result-1 (delay (long-calculation-1)))
(def result-2 (delay (long-calculation-2)))
(def result-3 (delay (long-calculation-3)))

(defn run-calcs []
  @(future
     @result-1
     @result-2
     @result-3))

Upvotes: 0

gsnewmark
gsnewmark

Reputation: 308

Manifold provides a way to create future with specific executor. It's not part of core Clojure lib, but it's still a high quality lib and probably a best option in case you need more flexibility dealing with futures than core lib provides (without resorting to Java interop).

Upvotes: 0

leetwinski
leetwinski

Reputation: 17849

if you want to sequentialize the calls to future you can use it manually like this:

(do @(future 1)
    @(future 2)
    @(future 3))

they would still possibly called in different threads, but the next one won't be called until the previous has finished. This is guaranteed by the @ (or deref function). This means that the thread in which you execute do form would be blocked with prev promise before it completes, and then spawn next one.

you can prettify it with macro like this:

(defmacro sequentialize [& futures]
  `(do ~@(map #(list `deref %) futures)))

user> (let [a (atom 1)]
        (sequentialize
         (future (swap! a #(* 10 %)))
         (future (swap! a #(+ 20 %)))
         (future (swap! a #(- %))))
        @a)
;;=> -30

this does exactly the same as manual do. Notice that mutations to a atom are in-order even if some threads run longer:

user> (let [a (atom 1)]
        (sequentialize
         (future (Thread/sleep 100)
                 (swap! a #(* 10 %)))
         (future (Thread/sleep 200)
                 (swap! a #(+ 20 %)))
         (future (swap! a #(- %))))
        @a)
;;=> -30

Upvotes: 1

erdos
erdos

Reputation: 3538

Clojure's future macro calls future-call function which uses a dedicated executor service. This means that you have no control to enforce a sequential execution.

On the other hand you can use promise instead of future objects and one future thread to sequentially deliver the results. Promise's API is similar to what futures provide. They have deref and realized? too.

The following code example has the subtasks executed sequentially on a new thread in the background while the immediately returned result of the function contains the promises to the computed values.

(defn start-async-calc []
  (let [f1 (promise)
        f2 (promise)
        f3 (promise)]
    (future
      (deliver f1 (task-1))
      (deliver f2 (task-2))
      (deliver f3 (task-3)))
    {:task1 f1
     :task2 f2
     :task3 f3}))

Upvotes: 2

Related Questions