几年没有完成任何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)))))))
当然,除此之外,事实证明并非如此简单 . 我想我已经把它缩小到没有正确清理的东西 . 基本上,运行main函数会产生不一致的结果 . 有时我运行它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行) . 线条以随机顺序出现的事实并不能始终完成所有工作吗? (因为我以某种方式滥用它们,但在哪里?)谢谢!
2 回答
这段代码有很多问题,让我快速浏览它们:
1)每次调用
(go ...)
时,你都会旋转一个将在线程池中执行的新"thread" . 该线程运行时未定义 .2)你不是在等待这些线程的完成,所以你可能(并且很有可能)最终会从文件中读取几行,在读取甚至发生之前向通道写入几行 .
3)您同时触发多次调用
append-to-file
(参见#2)这些函数未同步,因此可以让两个线程同时写入您的文件,覆盖彼此的结果 .4)由于您为每行读取创建了一个新的
go
块,因此它们可能会以不同于您预期的顺序执行,这意味着输出文件中的行可能出现故障 .我认为所有这些都可以通过避免与core.async相当普遍的反模式来解决:不要在无界或大循环内创建
go
块(或线程) . 通常这是做你不期望的事情 . 而是使用从文件读取的循环创建一个core.async/thread
(因为它正在执行IO,从不在go
块内执行IO)并写入通道,并从通道读取并写入输出文件 .将其视为由 Worker (
go
块)和传送带(通道)构建的装配线 . 如果你建造了一个工厂,你就不会组织所有人一次,他们之间有传送带和 Worker 之间的工作(或数据) . 您的员工应该是静态的,您的数据应该是移动的 ...当然,这是我对core.async的误用:
如果我关心看到输出中的所有数据,我在通道上使用 blocking 'take',当我想将值传递给我的I / O代码时 - 并且,正如所指出的那样,阻塞调用不应该在一个go block里面 . 我只需要改变一条线路:
从:
至: