Skip to content

Instantly share code, notes, and snippets.

@matthewdowney
Created August 13, 2019 19:19
Show Gist options
  • Save matthewdowney/5b3da8798df476d5a382e93fedb1d507 to your computer and use it in GitHub Desktop.
Save matthewdowney/5b3da8798df476d5a382e93fedb1d507 to your computer and use it in GitHub Desktop.
A `do` macro in clojure for concurrent execution of worker code with ordered results.
(import java.util.concurrent.CountDownLatch)
(defmacro dothreads
"Like (dotimes [i n] ...) except the body is executed concurrently, and the result is
a map from `i` to the result (or an exception if one is thrown).
Spins up `n-threads` threads and execute `body` in each with the thread number bound to
the given `tid-binding`, but use a CountDownLatch to make sure they all start at as close
to the same exact time as possible."
[[tid-binding n-threads] & body]
`(let [n-threads# ~n-threads
latch# (CountDownLatch. n-threads#)]
(->>
(for [~tid-binding (range n-threads#)]
(vector
~tid-binding
(future
(.countDown latch#) ;; Signal that we're ready
(.await latch#) ;; Await all other threads ready
~@body))) ;; Do whatever thing
(doall) ;; Make sure all threads start before we deref & block on their completion
(map (fn [[tid# task#]] [tid# (try @task# (catch Throwable t# t#))]))
(into {}))))
(comment
"For example..."
;; Execute the body in 4 different threads
(dothreads [n 4]
(let [started (System/currentTimeMillis)
my-name (.getName (Thread/currentThread))]
(println (format "My name is %s and I started at %s" my-name started))
{:name my-name :started started}))
;; Output is mixed on the same line because they happen concurrently
;; My name is clojure-agent-send-off-pool-31 and I started at 1565723836090My name is clojure-agent-send-off-pool-32 and I started at 1565723836090My name is clojure-agent-send-off-pool-30 and I started at 1565723836090
;; My name is clojure-agent-send-off-pool-29 and I started at 1565723836090
;; Return value contains more details about how concurrent they were...
;; =>
{0 {:name "clojure-agent-send-off-pool-29", :started 1565723836090},
1 {:name "clojure-agent-send-off-pool-30", :started 1565723836090},
2 {:name "clojure-agent-send-off-pool-31", :started 1565723836090},
3 {:name "clojure-agent-send-off-pool-32", :started 1565723836090}}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment