完成所有工作后关闭生产者端的渠道

时间:2017-10-27 22:15:26

标签: clojure

对于我的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

2 个答案:

答案 0 :(得分:1)

我会看一下core-async中的take函数。这就是它的文档说:

“返回一个频道,该频道最多会返回ch中的n个项目。在n个项目之后  已经退回,或ch已经关闭,退货渠道将关闭。 “

所以它会引导您进行一个简单的修复:而不是返回out-chan,您可以将其包装到take中:

(clojure.core.async/take (count chunked) out-chan)

应该有用。 另外我建议你重写你的例子,从阻止put / get到停车(<!>!)和threadgo / 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)))))