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" 工人之间的工作(或数据)。您的工作人员应该是静态的,而您的数据应该是移动的。
几年没有做任何 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" 工人之间的工作(或数据)。您的工作人员应该是静态的,而您的数据应该是移动的。