在Clojure中同时调用println
时,我发现它的行为与Java的System.out.println
不同。
我会用Java编写什么
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();
}
}
我在Clojure中解释为:
(doall (apply pcalls
(repeat 3 #(dotimes [i 5] (println "Hello iteration" (inc i))))))
不幸的是,在Clojure版本中,输出行经常出现交错:
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)
在Java never happens中,每条消息都打印在自己的行上。
你能解释一下Clojure的println
与Java的不同之处和原因,以及如何达到类似的线程安全&#34;在Clojure中使用println
的行为?
答案 0 :(得分:5)
clojure中的约定是锁定*out*
,它指的是打印到的位置。
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)
答案 1 :(得分:3)
在内部,println
将输出发送到作为*out*
的当前绑定值的writer。有几个理由认为这不是原子的:
println
函数是多个arity。如果交给多个对象,则会对*out*
进行多次写入。println
的调用被委托给名为print-method
的内部多方法(可以扩展为添加自定义类型的打印支持)。非字符串对象(尤其是集合类型)的print-method
实现可以对*out*
进行多次写入。这与Java的println
形成对比,后者将在对象上调用.toString
并进行单次写入。如果你想要原子打印,你可能必须明确地同步你的电话,例如:
(let [lock (Object.)]
(defn sync-println [& args]
(locking lock (apply println args))))
答案 2 :(得分:1)
Clojure 1.10中的新功能,还可以使用tap>
来同步println,例如:
(add-tap println)
(tap> [1 2 3 4])
;> [1 2 3 4]
现在,您可以发送给tap>
,以用线程安全的方式轻按接收的顺序打印:
(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)
请注意,tap>
是arity-1,因此您传递的内容不能超过一件事,这意味着在这种情况下,您必须首先使用str
来串联要打印的内容。
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}}
在后台,tap>
使用java.util.concurrent.ArrayBlockingQueue
来同步对其的呼叫。
还要注意,tap>
是异步的。因此,在打印内容时,它不会阻塞。这意味着,如果您在完成打印之前退出该应用程序,它将无法完成:
(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"
答案 3 :(得分:0)
为了完整性,使用Clojure的locking
的替代方法是依赖Java主机提供的System.out
(默认情况下绑定到*out*
)的同步。
(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))))
但请注意Synchronization and System.out.println的答案表明,从技术上讲,Java API并不保证System.out.println
的同步。当然,*out*
可以在Clojure中反弹。
答案 4 :(得分:0)
您还可以使用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))
这将确保您的输出不会交错,无论有多少线程同时调用aprintln
。