Clojure的pmap函数在进行URL获取操作时会生成多少个线程?

22

pmap函数的文档让我对于像在网络上获取一系列XML源文件这样的操作是否高效感到困惑。我不知道pmap会同时启动多少个抓取操作以及最大值是多少。

4个回答

25
如果您检查源代码,您会看到:
> (use 'clojure.repl)
> (source pmap)
(defn pmap
  "Like map, except f is applied in parallel. Semi-lazy in that the
  parallel computation stays ahead of the consumption, but doesn't
  realize the entire result unless required. Only useful for
  computationally intensive functions where the time of f dominates
  the coordination overhead."
  {:added "1.0"}
  ([f coll]
   (let [n (+ 2 (.. Runtime getRuntime availableProcessors))
         rets (map #(future (f %)) coll)
         step (fn step [[x & xs :as vs] fs]
                (lazy-seq
                 (if-let [s (seq fs)]
                   (cons (deref x) (step xs (rest s)))
                   (map deref vs))))]
     (step rets (drop n rets))))
  ([f coll & colls]
   (let [step (fn step [cs]
                (lazy-seq
                 (let [ss (map seq cs)]
                   (when (every? identity ss)
                     (cons (map first ss) (step (map rest ss)))))))]
     (pmap #(apply f %) (step (cons coll colls))))))

(+ 2 (.. Runtime getRuntime availableProcessors)) 是一个重要的提示。 pmap 将会获取前 (+ 2 processors) 个任务并通过 future 异步地运行它们。因此,如果你有2个核心,它将同时启动4个任务,试图保持一定的领先优势,但最大值应该是 2+n。

future 最终使用代理 I/O 线程池,该线程池支持无限数量的线程。当有工作需要处理时,线程池将会增长;若线程闲置,它也会缩小。


2
那么简短的答案是,pmap 对于分派大量网络调用和处理响应非常适用吗?有什么注意事项吗? - dan
4
我可能错了,但问题很可能是n+2个线程将阻塞等待网络响应。因此,您将无法获得足够的正在进行的请求以获得最大的吞吐量 - pmap确实适用于CPU密集型工作负载。如果您遇到此问题,则可以将每个请求调用包装在“future”中,它们将同时发送。 - mikera
6
并发编程往往没有简短的答案。我认为在这种情况下,pmap并不是最理想的选择。你实际上想要等待所有来源并行处理,而pmap会延迟启动第五个来源。除非你不一定想要处理完所有的来源,在这种情况下,pmap的惰性行为是好的。我建议你尝试对来源进行映射,并使用future来发起每个请求。 - Alex Miller
我想知道为什么在pmap中没有添加一个fn参数来以不同的方式控制线程数量(例如对于每个工作线程消耗的内存是一个问题的情况,以及一般情况下)。 - matanster

12

在Alex出色的回答基础上,解释了pmap的工作原理,这里是针对你情况的建议:

(doall
  (map
    #(future (my-web-fetch-function %))
    list-of-xml-feeds-to-fetch))

理由:

  • 你希望尽可能有多个工作同时进行,因为大多数工作都会在网络 IO 上阻塞。
  • Future 会为每个请求触发一个异步工作,在线程池中处理。你可以让 Clojure 智能地处理这些工作。
  • 映射上的 doall 将强制评估完整序列(即启动所有请求)。
  • 你的主线程可以立即开始解引用 futures,并因此在个别结果返回时继续取得进展。

1
我认为futures使用无界线程池,因此在大量的feeds上运行可能会导致问题。 - Glen
1
此外,您可能希望再次映射以deref未来,以便您知道何时完成所有操作。 - Joe

3
没有时间写长篇回复,但是有一个Clojure.contrib http-agent可以创建每个get/post请求作为它自己的代理。这样你就可以同时发送一千个请求,它们都会并行运行,并在结果到达时完成。

2

看起来 pmap 的操作似乎每次只能同时运行 32 个线程,无论您拥有多少处理器,问题在于 map 会比计算快出 32 倍,并且 futures 是在它们自己的线程中启动的。(示例) (defn samplef [n] (println "开始 " n) (Thread/sleep 10000) n) (def result (pmap samplef (range 0 100)))

; 您将等待 10 秒钟并看到 32 个打印,然后当您取第 33 个时,另外 32 个打印,这意味着您每次最多可以并发运行 32 个线程 ; 对我来说这不是完美的 ; SALUDOS Felipe


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接