我正在学习core.async
并编写了一个简单的制作人消费者代码:
(ns webcrawler.parallel
(:require [clojure.core.async :as async
:refer [>! <! >!! <!! go chan buffer close! thread alts! alts!! timeout]]))
(defn consumer
[in out f]
(go (loop [request (<! in)]
(if (nil? request)
(close! out)
(do (print f)
(let [result (f request)]
(>! out result))
(recur (<! in)))))))
(defn make-consumer [in f]
(let [out (chan)]
(consumer in out f)
out))
(defn process
[f s no-of-consumers]
(let [in (chan (count s))
consumers (repeatedly no-of-consumers #(make-consumer in f))
out (async/merge consumers)]
(map #(>!! in %1) s)
(close! in)
(loop [result (<!! out)
results '()]
(if (nil? result)
results
(recur (<!! out)
(conj results result))))))
当我通过Emacs提供的调试器中的process
函数进入时,此代码可以正常工作。苹果酒。
(process (partial + 1) '(1 2 3 4) 1)
(5 4 3 2)
但是,如果我自己运行它(或者在调试器中点击继续),我会得到一个空的结果。
(process (partial + 1) '(1 2 3 4) 1)
()
我的猜测是,在第二种情况下出于某种原因,生产者在退出之前并没有等待消费者,但我不确定为什么。谢谢你的帮助!
答案 0 :(得分:3)
问题是您对map
的调用是懒惰的,并且在某些事情要求结果之前不会运行。您的代码中没有任何内容。
有两种解决方案:
(1)使用急切的功能mapv
:
(mapv #(>!! in %1) items)
(2)使用doseq
,用于副作用操作(比如在通道上放置值):
(doseq [item items]
(>!! in item))
两者都会起作用并产生输出:
(process (partial + 1) [1 2 3 4] 1) => (5 4 3 2)
P.S。您在(defn consumer ...)
(print f)
在输出中产生大量噪音:
<#clojure.core$partial$fn__5561 #object[clojure.core$partial$fn__5561 0x31cced7
"clojure.core$partial$fn__5561@31cced7"]>
重复5次 背靠背。你可能想避免这种情况,因为打印功能“refs”对于人类读者来说是无用的。
此外,调试打印输出通常应该使用println
,以便您可以看到每个打印输出的开始和结束位置。
答案 1 :(得分:1)
我要采取安全措施,这是由map
的懒惰行为引起的,这条线正在产生副作用:
(map #(>!! in %1) s)
因为您从未明确使用结果,所以它永远不会运行。将其更改为使用mapv
,这是严格的,或者更准确地说,使用doseq
。切勿使用map
来运行副作用。这意味着懒洋洋地改变一个列表,滥用它会导致这样的行为。
那为什么它在调试时有效呢?我猜测是因为调试器强迫评估作为其操作的一部分,它正在掩盖问题。
答案 2 :(得分:1)
正如你可以从docstring map
读取的那样,返回一个懒惰的序列。我认为最好的方法是使用dorun
。以下是clojuredocs的一个例子:
;;map a function which makes database calls over a vector of values
user=> (map #(db/insert :person {:name %}) ["Fred" "Ethel" "Lucy" "Ricardo"])
JdbcSQLException The object is already closed [90007-170] org.h2.message.DbE
xception.getJdbcSQLException (DbException.java:329)
;;database connection was closed before we got a chance to do our transactions
;;lets wrap it in dorun
user=> (dorun (map #(db/insert :person {:name %}) ["Fred" "Ethel" "Lucy" "Ricardo"]))
DEBUG :db insert into person values name = 'Fred'
DEBUG :db insert into person values name = 'Ethel'
DEBUG :db insert into person values name = 'Lucy'
DEBUG :db insert into person values name = 'Ricardo'
nil