clojure core.async - 意外的不一致

clojure core.async - unexpected inconsistencies

几年没有做任何 Clojure,所以决定回去而不是忽略 core.async 这次)非常酷的东西,但它几乎立即让我感到惊讶。现在,我明白当涉及多个线程时存在固有的不确定性,但这里有比这更大的东西在起作用。

我的非常简单的示例的源代码,我在其中尝试将行从 STDIN 复制到文件:

(defn append-to-file
  "Write a string to the end of a file"
  ([filename s]
   (spit filename (str s "\n")
         :append true))
  ([s]
   (append-to-file "/tmp/journal.txt" s)))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Initializing..")
  (let [out-chan (a/chan)]
    (loop [line (read-line)]
      (if (empty? line) :ok
          (do
            (go (>! out-chan line))
            (go (append-to-file (<! out-chan)))
            (recur (read-line)))))))

除了,当然,事实证明这并不是那么简单。我想我已经将它缩小到 某些东西 没有正确清理。基本上,运行宁主要功能产生不一致的结果。有时我 运行 它 4 次,并在输出中看到 12 行。但有时,4 运行 只会产生 10 行。或者像下面这样,3 次,6 行:

akamac.home ➜  coras git:(master) ✗ make clean
cat /dev/null > /tmp/journal.txt
lein clean
akamac.home ➜  coras git:(master) ✗ make compile
lein uberjar
Compiling coras.core
Created /Users/akarpov/repos/coras/target/uberjar/coras-0.1.0-SNAPSHOT.jar
Created /Users/akarpov/repos/coras/target/uberjar/coras-0.1.0-SNAPSHOT-standalone.jar
akamac.home ➜  coras git:(master) ✗ make run    
java -jar target/uberjar/coras-0.1.0-SNAPSHOT-standalone.jar < resources/input.txt
Initializing..
akamac.home ➜  coras git:(master) ✗ make run
java -jar target/uberjar/coras-0.1.0-SNAPSHOT-standalone.jar < resources/input.txt
Initializing..
akamac.home ➜  coras git:(master) ✗ make run
java -jar target/uberjar/coras-0.1.0-SNAPSHOT-standalone.jar < resources/input.txt
Initializing..
akamac.home ➜  coras git:(master) ✗ make check  
cat /tmp/journal.txt
line a
line z
line b
line a
line b
line z

(基本上,有时 运行 产生 3 行,有时 0 行,有时 1 或 2 行)。 线条以随机顺序出现的事实并没有打扰我 - go 块以 concurrent/threaded 的方式做事,所有的赌注都没有了。但为什么他们不所有 的工作所有 的时间? (因为我以某种方式滥用它们,但是在哪里?) 谢谢!

.. 当然,这是我对 core.async 的误用:

如果我想看到输出中的所有数据,我必须在通道上使用阻塞 'take' ,当我想将值传递给我的 I/O 代码时——正如所指出的那样,阻塞调用不应在 go 块内。我只需要一行更改:

来自:

(go (append-to-file (<! out-chan)))

至:

(append-to-file (<!! out-chan))

这段代码有很多问题,让我快速浏览一下:

1) 每次调用 (go ...) 时,您都在旋转一个新的 "thread",它将在线程池中执行。未定义此线程何时 运行.

2) 你不是在等待这些线程的完成,所以你有可能(而且很可能)最终会从文件中读取几行,将几行写入通道,甚至在读取之前发生。

3) 您同时触发了对 append-to-file 的多个调用(请参阅#2)这些函数不同步,因此多个线程可能会同时追加。由于在大多数操作系统中对文件的访问是不协调的,因此两个线程可能同时写入您的文件,从而覆盖彼此的结果。

4) 由于您正在为读取的每一行创建一个新的 go 块,因此它们的执行顺序可能与您预期的不同,这意味着输出文件中的行可能是乱序的.

我认为所有这些都可以通过避免使用 core.async 的相当常见的反模式来稍微解决:不要在无界或大循环中创建 go 块(或线程)。通常这是在做一些你意想不到的事情。而是创建一个 core.async/thread 循环,从文件中读取(因为它正在执行 IO,永远不要在 go 块内执行 IO)并写入通道,以及从通道读取并写入的循环输出文件。

将其视为由工人(go 块)和传送带(通道)组成的装配线。如果你建了一个工厂,你就不会有一堆人,然后把他们配对说 "you take one item, when you're done hand it to him"。相反,你会组织所有的人一次,他们之间有传送带,"flow" 工人之间的工作(或数据)。您的工作人员应该是静态的,而您的数据应该是移动的。