对于我的Mandelbrot资源管理器项目,我需要运行几个昂贵的工作,理想情况是并行工作。我决定尝试分块工作,并在自己的thread
中运行每个块,最后结束类似
(defn point-calculator [chunk-size points]
(let [out-chan (chan (count points))
chunked (partition chunk-size points)]
(doseq [chunk chunked]
(thread
(let [processed-chunk (expensive-calculation chunk)]
(>!! out-chan processed-chunk))))
out-chan))
其中points
是要测试的[实,虚]坐标的列表,expensive-calculation
是一个获取块的函数,并测试块中的每个点。每个块可能需要很长时间才能完成(可能需要一分钟或更长时间,具体取决于块大小和作业数量)。
在我的消费者端,我正在使用
(loop []
(when-let [proc-chunk (<!! result-chan)]
; Do stuff with chunk
(recur)))
使用每个已处理的块。现在,由于频道仍处于打开状态,因此在最后一个块被消耗时会阻塞。
我需要一种在作业完成时关闭频道的方法。由于生产者循环的异步性,这证明是困难的。我不能简单地在close!
之后放置一个doseq
,因为循环没有阻塞,并且我不能只在最后索引的作业完成时关闭,因为订单是不确定的。 / p>
我能想到的最好的想法是维持(atom #{})
个工作,并在完成每个工作时disj
。然后我可以检查循环中的设置大小,并在{0}时检查close!
,或者将监视附加到原子并检查那里。
但这似乎非常虚伪。有没有更惯用的处理方式?这种情况是否表明我错误地使用async
?
答案 0 :(得分:1)
我会看一下core-async
中的take函数。这就是它的文档说:
“返回一个频道,该频道最多会返回ch中的n个项目。在n个项目之后 已经退回,或ch已经关闭,退货渠道将关闭。 “
所以它会引导您进行一个简单的修复:而不是返回out-chan
,您可以将其包装到take
中:
(clojure.core.async/take (count chunked) out-chan)
应该有用。
另外我建议你重写你的例子,从阻止put / get到停车(<!
,>!
)和thread
到go / go-loop
,这是核心异步的更惯用的用法。
答案 1 :(得分:0)
您可能希望使用async / pipeline(-blocking)来控制并行性。并且在复制完所有块后,使用aysnc / on-chan自动关闭输入通道。
E.g。下面的例子显示当并行度设置为16时经过的时间提高了16倍。
(defn expensive-calculation [pts]
(Thread/sleep 100)
(reduce + pts))
(time
(let [points (take 10000 (repeatedly #(rand 100)))
chunk-size 500
inp-chan (chan)
out-chan (chan)]
(go-loop [] (when-let [res (<! out-chan)]
;; do stuff with chunk
(recur)))
(pipeline-blocking 16 out-chan (map expensive-calculation) inp-chan)
(<!! (onto-chan inp-chan (partition-all chunk-size points)))))