The two resources that helped me most with concurrency and parallelism
are "Java Concurrency in Practice" and "ZeroMQ — The Guide".
Introductory Go books are also enlightening.
Once you have a clear understanding of the underlying concepts in
general, understanding how they are accessible in Clojure is really
just about knowing what is available in the standard library. Remember
that that includes all of Java, too. Also keep in mind that a
0-argument Clojure fn is a java.lang.Runnable and a
java.util.concurrent.Callable.
Here's an example of using an atom with multiple threads:
(def counter (atom 0))
(let [threads (repeatedly 10 (fn [] (Thread.
#(dotimes [_ 100] (swap! counter inc)))))]
(->> threads
(map #(.start %))
doall)
(->> threads
(map #(.join %))
doall)
(println @counter))
The point of the atom should be slightly clearer: it is indeed a
"mutable value" (we don't really use these two words together in
Clojure; things are either "mutable reference" or "immutable value")
like in any other language, except that you can change it from
multiple threads without any problem, because the updates are atomic —
hence the name.
The above code is using bare Java threads, which is not very
idiomatic. Usually, there are better options within Clojure itself
(for this simple example, using future instead of bare threads would
yield slightly more compact code), but it's hard to know which one to
suggest without more information on your use-case.
You basically have two ways of coordinating threads: message-passing
or shared memory. The point of atoms is to support a safe way to share
memory between threads, by providing a reference to a (supposedly)
immutable value. If you're more of the message-passing inclination,
you can use core.async channels and >!! (blocking put) and <!!
(blocking take). If you're using real threads, you don't even need to
dig into the go macro.
Clojure also offers a lot of additional functions and types for more
specific concurrency use-cases. For example, the future-call function
takes a function of zero arguments, creates a thread, starts the
thread, and returns a reference that can be later dereferenced to get
the value returned by the function that runs in the separate thread;
it also implements caching, should the future value be dereferenced
multiple times. For example:
(defn slow-fn []
(Thread/sleep 1000)
(println "hey!")
42)
(def fut (future-fn slow-fn)) ;; Thread has started.
(deref fut) ;; Would be a blocking call
;; A second after the def, the message is printed
(deref fut) ;; is not a blocking call anymore, and returns 42 directly
You have lots of small functions and macros like that — delay,
promise, future, ref, agent, pmap, reducers, etc, but without more
information about either which function/concept you're trying to
understand or what problem you're trying to solve it's hard to help
you more than that.
On 7 April 2016 at 15:00, Niels van Klaveren