我根本没有在Clojure中使用多线程,所以不确定从哪里开始。
我有一个doseq
,其身体可以并行运行。我想要的是总是有3个线程运行(留下1个核心空闲),并行地评估主体直到范围耗尽。没有共享状态,没有什么复杂的 - 相当于Python的多处理就好了。
类似于:
(dopar 3 [i (range 100)]
; repeated 100 times in 3 parallel threads...
...)
我应该从哪里开始寻找?有这个命令吗?标准包装?一个很好的参考?
到目前为止,我找到了pmap
,并且可以使用它(我如何一次限制为3个?looks like it uses 32 at a time - 不,来源说2 +处理器数量),但似乎这是一个应该已存在于某处的基本原语。
澄清:我真的想控制线程的数量。我有长时间运行的进程并使用相当数量的内存,因此创建大量数据并希望事情正常运行不是一个好方法(example which uses a significant chunk available mem)。
update :开始编写一个执行此操作的宏,我需要一个信号量(或一个互斥量,或一个我可以等待的原子)。 Clojure中是否存在信号量?或者我应该使用ThreadPoolExecutor?从Java中提取这么多内容似乎很奇怪 - 我认为Clojure中的并行编程应该很容易......也许我正在考虑这完全错误的方式?嗯。剂?
答案 0 :(得分:6)
好的,我认为我想要的是每个循环都有一个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也有点摇晃)。 Related blog post
答案 1 :(得分:4)
为什么不使用pmap?你仍然无法控制线程池,但是编写使用代理的自定义宏(为什么不是期货?)的工作要少得多。
答案 2 :(得分:4)
我遇到了类似的问题:
核心pmap
函数仅满足最后两个假设。
这是一个满足这些假设的实现,使用标准Java线程池ExecutorService
和CompletionService
以及输入流的一些分区:
(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并发类的文档:
答案 3 :(得分:3)
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
(这是懒惰的)。
答案 4 :(得分:3)
答案 5 :(得分:2)
不确定它是否是惯用的,因为我还是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))))
答案 6 :(得分:0)
使用管道和渠道。如果您的操作是IO绑定,那么这是一个更好的选择,因为pmap的池绑定到CPU数量。
另一个不错的选择是使用代理和send-off,它使用下面的cachedThredPoolExecutor。