繁体   English   中英

Clojure的平行剂量

[英]Parallel doseq for Clojure

我根本没有在Clojure中使用多线程,所以不确定从哪里开始。

我有一个doseq它的身体可以并行运行。 我想要的是总是有3个线程运行(留下1个核心空闲),并行地评估主体直到范围耗尽。 没有共享状态,没有什么复杂的 - 相当于Python的多处理就可以了。

所以类似于:

(dopar 3 [i (range 100)]
  ; repeated 100 times in 3 parallel threads...
  ...)

我该在哪里开始寻找? 有这个命令吗? 标准包装? 一个很好的参考?

到目前为止,我已经找到了pmap ,并且可以使用它(我如何一次限制为3? 看起来它一次使用32个 - 不,源说2 +处理器数量),但似乎这是一个基本的应该已经存在于某处的原语。

澄清 :我真的想控制线程的数量。 我有长时间运行的进程并且使用相当数量的内存,因此创建大量数据并希望事情正常运行OK并不是一个好方法( 使用重要块可用内存的示例 )。

更新 :开始写一个执行此操作的宏,我需要一个信号量(或一个互斥量,或者我可以等待的原子)。 Clojure中是否存在信号量? 或者我应该使用ThreadPoolExecutor? 从Java中提取这么多内容似乎很奇怪 - 我认为Clojure中的并行编程应该很容易......也许我正在考虑这完全错误的方式? 嗯。 代理?

好吧,我认为我想要的是为每个循环设置一个agent ,并使用send将数据发送给代理。 使用send触发的代理程序是从一个线程池运行的,所以这个数字在某种程度上是有限的(它不会给出正好三个线程的细粒度控制,但现在必须这样做)。

[Dave Ray在评论中解释:控制池大小我需要自己编写]

(defmacro dopar [seq-expr & body]
  (assert (= 2 (count seq-expr)) "single pair of forms in sequence expression")
  (let [[k v] seq-expr]
    `(apply await
       (for [k# ~v]
         (let [a# (agent k#)]
           (send a# (fn [~k] ~@body))
         a#)))))

可以使用如下:

(deftest test-dump
  (dopar [n (range 7 11)]
    (time (do-dump-single "/tmp/single" "a" n 10000000))))

好极了! 作品! 我好棒! (好吧,Clojure也有点摇晃)。 相关博文

你为什么不用pmap? 你仍然无法控制线程池,但它比编写使用代理的自定义宏(为什么不是期货?)要少得多。

我有以下要求的类似问题:

  1. 控制使用的线程数;
  2. 对线程池的管理不了解;
  3. 不需要保留任务的顺序;
  4. 任务的处理时间可能不同,因此不得保留任务的顺序,但应提前返回先前完成的任务;
  5. 懒惰地评估并提交输入序列;
  6. 输入序列中的元素不应读出边界,但应根据返回的结果进行缓冲和读取,以避免内存不足问题。

核心pmap函数仅满足最后两个假设。

这是一个满足这些假设的实现,使用标准Java线程池ExecutorServiceCompletionService以及输入流的一些分区:

(require '[clojure.tools.logging :as log])

(import [java.util.concurrent ExecutorService ExecutorCompletionService 
                              CompletionService Future])

(defn take-seq
  [^CompletionService pool]
  (lazy-seq
   (let [^Future result (.take pool)]
     (cons (.get result)
           (take-seq pool)))))

(defn qmap
  [^ExecutorService pool chunk-size f coll]
  (let [worker (ExecutorCompletionService. pool)]
    (mapcat
     (fn [chunk]
       (let [actual-size (atom 0)]
         (log/debug "Submitting payload for processing")
         (doseq [item chunk]
           (.submit worker #(f item))
           (swap! actual-size inc))
         (log/debug "Outputting completed results for" @actual-size "trades")
         (take @actual-size (take-seq worker))))
     (partition-all chunk-size coll))))

可以看出, qmap不会实例化线程池本身,而只会实例化ExecutorCompletionService 例如,这允许传入固定大小的ThreadPoolExecutorService 此外,由于qmap返回一个惰性序列,它不能也不能管理线程池资源本身。 最后, chunk-size允许限制输入序列的多少元素被实现并一次作为任务提交。

以下代码演示了正确的用法:

(import [java.util.concurrent Executors])

(let [thread-pool (Executors/newFixedThreadPool 3)]
  (try
    (doseq [result (qmap thread-pool
                         ;; submit no more than 500 tasks at once
                         500 
                         long-running-resource-intensive-fn
                         unboundedly-large-lazy-input-coll)]
      (println result))
    (finally
      ;; (.shutdown) only prohibits submitting new tasks,
      ;; (.shutdownNow) will even cancel already submitted tasks.
      (.shutdownNow thread-pool))))

以下是一些使用过的Java并发类的文档:

在大多数情况下, pmap实际上可以正常工作 - 它使用一个线程池,为您的机器提供合理数量的线程。 我不打算尝试创建自己的机制来控制线程数,除非你有真正的基准证据证明默认值导致了问题。

话虽如此,如果你真的想限制到最多三个线程,一个简单的方法是在范围的3个子集上使用pmap:

(defn split-equally [num coll] 
  "Split a collection into a vector of (as close as possible) equally sized parts"
  (loop [num num 
         parts []
         coll coll
         c (count coll)]
    (if (<= num 0)
      parts
      (let [t (quot (+ c num -1) num)]
        (recur (dec num) (conj parts (take t coll)) (drop t coll) (- c t)))))) 

(defmacro dopar [thread-count [sym coll] & body]
 `(doall (pmap 
    (fn [vals#]
      (doseq [~sym vals#]
        ~@body))  
    (split-equally ~thread-count ~coll))))

注意使用doall ,这是强制评估pmap (这是懒惰的)所必需的。

现在实际上有一个图书馆可以做到这一点。 从他们的github

claypoole库提供了基于线程池的Clojure函数的并行版本,例如pmapfuturefor

它提供了相同的有序/无序版本。

不确定它是否是惯用的,因为我仍然是Clojure的初学者,但以下解决方案适用于我,它看起来也非常简洁:

(let [number-of-threads 3
      await-timeout 1000]
  (doseq [p-items (partition number-of-threads items)]
    (let [agents (map agent p-items)]
      (doseq [a agents] (send-off a process))
      (apply await-for await-timeout agents)
      (map deref agents))))

使用管道和渠道。 如果您的操作是IO绑定,这是一个更好的选项,因为pmap的池绑定到CPU数量。

另一个好的选择是使用代理和send-off,它使用下面的cachedThredPoolExecutor。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM