glts
glts

Reputation: 22674

Make Clojure's println "thread-safe" in the same way as in Java

While playing around with concurrent calls to println in Clojure I found that its behaviour is different from Java's System.out.println.

What in Java I would write

class Pcalls {
    public static void main(String[] args) {
        Runnable[] fns = new Runnable[3];
        for (int i = 0; i < 3; i++) {
            fns[i] = new Runnable() {
                @Override public void run() {
                    for (int i = 1; i <= 5; i++) {
                        System.out.println("Hello iteration " + i);
                    }
                }
            };
        }
        for (Runnable fn : fns) new Thread(fn).start();
    }
}

I paraphrased in Clojure as:

(doall (apply pcalls
              (repeat 3 #(dotimes [i 5] (println "Hello iteration" (inc i))))))

Unfortunately, in the Clojure version the output lines often appear interleaved:

Hello iterationHello iteration  1
Hello iteration Hello iteration 2
Hello iteration 3
1
Hello iteration 4
1
Hello iteration Hello iteration5
 Hello iteration 2
Hello iteration 23

Hello iteration Hello iteration 4
3Hello iteration 
5
Hello iteration 4
Hello iteration 5
(nil nil nil)

In Java this never happens, every message is printed on its own line.

Can you explain how and why Clojure's println differs from Java's, and how to arrive at a similar kind of "thread-safe" behaviour with println in Clojure?

Upvotes: 1

Views: 1926

Answers (5)

Didier A.
Didier A.

Reputation: 4816

New in Clojure 1.10, one can also make use of tap> to synchronize println, as such:

(add-tap println)
(tap> [1 2 3 4])
;> [1 2 3 4]

Now you can send to tap> to print in the order tap receives in a thread safe manner:

(doall (apply pcalls
              (repeat 3 #(dotimes [i 5] (tap> (str "Hello iteration" " " (inc i)))))))
Hello iteration 1
Hello iteration 1
Hello iteration 2
Hello iteration 3
Hello iteration 4
Hello iteration 5
Hello iteration 1
Hello iteration 2
Hello iteration 3
Hello iteration 4
Hello iteration 5
Hello iteration 2
Hello iteration 3
Hello iteration 4
Hello iteration 5
(nil nil nil)

Just note that tap> is arity-1, so you can't pass it more than one thing, which means in this case you have to use str first to concatenate what you want printed.

With `tap>`, you can also have it do synchronized pretty printing:

(add-tap (bound-fn* clojure.pprint/pprint))
(tap> {:a 100 :b 200 :c 300 :d 200 :f 400 :g 400000000 :h 3992 :l {:k 10203 :f 39945 :o 29394}})

{:a 100,
 :b 200,
 :c 300,
 :d 200,
 :f 400,
 :g 400000000,
 :h 3992,
 :l {:k 10203, :f 39945, :o 29394}}

Under the hood, tap> uses a java.util.concurrent.ArrayBlockingQueue to synchronize the calls to it.

Also note that tap> is asynchronous. So while things are being printed, it won't block. That means that if you quit the app before it is done printing, it won't finish:

(doall (apply pcalls
              (repeat 3 #(dotimes [i 5] (tap> (str "Hello iteration" " " (inc i)))))))
(System/exit 0)

"Hello iteration 1"
"Hello iteration 2"
"Hello iteration 3"
"Hello iteration 4"

Upvotes: 4

cap10morgan
cap10morgan

Reputation: 121

You can also solve this problem with core.async:

(def print-chan (chan 10))

(defn aprintln [& message]
  (>!! print-chan message))

(defn start-printer! [] (thread (while true
                                  (apply println (<!! print-chan)))))

(defn do-a-thing [] (aprintln "Doing a thing"))

(defn do-another-thing [] (aprintln "Doing another thing"))

(defn -main []
  (start-printer!)
  (future (do-a-thing))
  (do-another-thing))

This will ensure that your outputs don't interleave, no matter how many threads call aprintln at once.

Upvotes: 0

glts
glts

Reputation: 22674

For completeness, an alternative to using Clojure's locking is to rely on the synchronisation of System.out (to which *out* is bound by default) provided by the Java host.

(doall (apply pcalls
              (repeat 3 #(dotimes [i 5]
                           (.println *out* (str "Hello iteration " (inc i)))))))
(defn out-println [& args]
  (.println *out* (apply str (interpose \space args))))

But note that the answers to Synchronization and System.out.println suggest that technically the Java API does not guarantee synchronisation for System.out.println. And of course *out* can be rebound in Clojure.

Upvotes: 0

Alex
Alex

Reputation: 13941

Internally, println sends output to the writer that is the currently-bound value for *out*. There are a couple of reasons that calls to this are not atomic:

  1. The println function is multiple arity. If handed multiple objects, it makes multiple writes to *out*.
  2. Calls to println are delegated to an internal multimethod called print-method (which can be extended to add print support for custom types). The print-method implementation for non-string objects, especially collection types, can make multiple writes to *out*. This is in contrast to Java's println which will call .toString on the object and make a single write.

If you want atomic println's, you'll probably have to explicitly synchronize your calls, e.g.:

(let [lock (Object.)]
  (defn sync-println [& args]
    (locking lock (apply println args))))

Upvotes: 4

noisesmith
noisesmith

Reputation: 20194

A convention in clojure is to lock *out*, which refers to the location printed to.

user> (doall (apply pcalls
            (repeat 3 #(dotimes [i 5]
                             (locking *out*
                               (println "Hello iteration" (inc i)))))))
Hello iteration 1
Hello iteration 1
Hello iteration 2
Hello iteration 3
Hello iteration 4
Hello iteration 5
Hello iteration 1
Hello iteration 2
Hello iteration 3
Hello iteration 4
Hello iteration 5
Hello iteration 2
Hello iteration 3
Hello iteration 4
Hello iteration 5
(nil nil nil)

Upvotes: 6

Related Questions