1

在玩 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 中这永远不会发生,每条消息都打印在自己的行上。

您能否解释一下 Clojureprintln与 Java 的不同之处和原因,以及如何在 Clojure 中实现类似的“线程安全”行为println

4

5 回答 5

6

clojure 中的一个约定是 lock *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)
于 2013-09-06T16:56:03.690 回答
3

在内部,println将作为当前绑定值的输出发送到编写器*out*。有几个原因调用 this 不是原子的:

  1. println功能是多重的。如果交了多个对象,它会多次写入*out*.
  2. 调用println被委托给一个内部多方法调用print-method(可以扩展以添加对自定义类型的打印支持)。print-method非字符串对象的实现,尤其是集合类型,可以多次写入*out*. 这与 Java 的相反,println后者将调用.toString对象并进行一次写入。

如果您想要原子 println,您可能必须显式同步您的调用,例如:

(let [lock (Object.)]
  (defn sync-println [& args]
    (locking lock (apply println args))))
于 2013-09-06T16:56:24.940 回答
2

Clojure 1.10 中的新功能,也可以tap>用来同步 println,如下所示:

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

tap>现在,您可以按照线程安全的方式按 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>使用 ajava.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"
于 2019-12-27T06:17:53.600 回答
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 和 System.out.println的回答表明,从技术上讲,Java API 不保证System.out.println. 当然*out*也可以在 Clojure 中反弹。

于 2013-09-07T17:17:47.037 回答
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一次调用。

于 2013-11-21T18:58:55.273 回答