2

在下面的代码中,我使用了一个 go 循环,它在通道上侦听并将它读取的值写入代理。然后,在同一个循环中,日志到文件功能应该将代理上的每个项目写入文件。但是,文件的输出是不寻常的,因为具有 [8 6 5 13] 的代理可能会将其内容作为 865n13 吐到文件中。如果我从记录器函数中删除日志到文件调用并单独调用它,有时文件输出很好,有时不是。有人可以向我解释发生了什么,并可能向我展示一种安全的方法来证明一致的行为吗?我知道不使用代理可能是实现一致行为的最佳方式。我只是想知道,本着学习的精神,使用代理可以实现什么,不可以实现什么。提前感谢!

(defn log-to-file [file] (for [i (deref log)] (spit file (str "\n" i) :append true)))

(defn logger [file]
  (go 
   (loop []
     (when-let [v (<! print-chan)]
       (send log conj v)
       (log-to-file file)
       (recur)))))

;; testing it out

(def log (agent []))
(>!! print-chan 5)
(logger "resources/test.txt")
(deref log)

编辑:

这是根据下面接受的答案中的评论重写的代码。我完全不建议使用此代码,或者就此而言,在 go 循环中使用具有副作用的代理。

(def ch (chan))
(def a (agent []))

(defn double-async-print []
    (go-loop []
       (when-let [x (<! ch)]
         (send a conj x)
         (print (clojure.string/join "\n" @a)))
     (recur)))

(go (doseq [n (range 10)] (>! ch n)))

(double-async-print) 

    ;=>jsync 0.1.0-SNAPSHOT[stdout]:    
    00
    10
    1
    20
    1
    2
    30
    1
    2
    3
    40
    1
   ...
4

1 回答 1

3

我怀疑有很多事情正在发生相互作用,导致你的困惑。问题本身并不是在 go-loop 中使用代理。

send是异步的,因此不能保证您对 in 的调用deref会在循环内调用log-to-file时看到更改。log-to-file

此外,您错误地使用for了副作用代码(文件 io with spit)。看起来您正在尝试将其类似于其他语言中的命令式 for-each 循环使用。doseq是用于处理副作用序列的正确 clojure 构造(请参阅:Clojure 中的 doseq 和 for 之间的差异)。

您也没有采取任何措施来删除在代理中构建的向量的内容,因此即使事情同步运行,您也可以例如在文件中添加一个:a、:b 和 :c,例如:

:a
:a
:b
:a
:b
:c

从问题中不清楚这是否是您对代码的期望行为。


clojure.string/join 的旁白:

一般来说,doseq如果您只是希望将向量或其他集合输出到换行符分隔的文件,那么即使您正确使用,如上所述使用 spit 也是非常低效的。最好使用clojure.string/join例如

user>  (print (clojure.string/join "\n" [:a :b :c :d]))
:a
:b
:c
:d

您可以使用该pprint函数通过转义查看实际传递给 print 的字符串是什么样的(pprint比这种用法更通用,并为各种 clojure 数据提供了一个很好的可视化格式):

user> (clojure.pprint/pprint (clojure.string/join "\n" [:a :b :c :d]))
":a\n:b\n:c\n:d"
于 2014-05-13T14:22:10.363 回答