首页 文章

clojure core.async - 意外的不一致

提问于
浏览
1

几年没有完成任何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 回答

  • 4

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

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

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

    3)您同时触发多次调用 append-to-file (参见#2)这些函数未同步,因此可以让两个线程同时写入您的文件,覆盖彼此的结果 .

    4)由于您为每行读取创建了一个新的 go 块,因此它们可能会以不同于您预期的顺序执行,这意味着输出文件中的行可能出现故障 .

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

    将其视为由 Worker ( go 块)和传送带(通道)构建的装配线 . 如果你建造了一个工厂,你就不会组织所有人一次,他们之间有传送带和 Worker 之间的工作(或数据) . 您的员工应该是静态的,您的数据应该是移动的 .

  • 0

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

    如果我关心看到输出中的所有数据,我在通道上使用 blocking 'take',当我想将值传递给我的I / O代码时 - 并且,正如所指出的那样,阻塞调用不应该在一个go block里面 . 我只需要改变一条线路:

    从:

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

    至:

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

相关问题