我正在编写的代码采用了 pmap
产生的一些懒惰的结果,并将它们绘制到 BufferedImage
上.三天来,我一直在试图弄清楚为什么绘图突然开始卡住并最终在大约 1/3 的过程中停止。
我终于将范围缩小到我在另一个线程中循环大量数据的事实。
这是我想出的最好的 MCVE:
(ns mandelbrot-redo.irrelevant.write-image-mcve
(:import [java.awt.image BufferedImage]
(java.util.concurrent Executors Executor)))
(defn lazy-producer [width height]
(for [y (range height)
x (range width)]
[x y (+ x y)]))
; This works fine; finishing after about 5 seconds when width=5000
(defn sync-consumer [results width height]
(time
(doseq [[i result] (map vector (range) results)]
(when (zero? (rem i 1e6))
(println (str (double (/ i (* width height) 0.01)) "%")))
((fn boop [x] x) result)))) ; Data gets consumed here
; This gets to ~30%, then begins being interupted by 1-4 second lags
(defn async-consumer [results width height]
(doto
(Thread. ^Runnable
(fn []
(sync-consumer results width height)
(println "Done...")))
(.start)))
(defn -main []
(let [width 5000
height (int (* width 2/3))]
(-> (lazy-producer width height)
(async-consumer width height))))
当
-main
使用 sync-consumer
运行,它会在几秒钟后完成。与 async-consumer
然而,它达到约 25%,然后开始缓慢爬行,直到最后打印的百分比为 30%。如果我离开它,我会得到一个 OOME。如果我使用显式
Thread.
,或在 async-consumer
中使用本地线程池,它挂起并崩溃。如果我使用 future
然而,它完成得很好,就像 sync-consumer
.我得到的唯一提示是,当我在 VisualVM 中运行它时,我看到我有
Long
的失控分配。 s 使用异步版本时:同步版本显示峰值为
Long
相比之下,s 一次约为 45mb。CPU使用率也大不相同:
有大量的 GC 峰值,但它似乎不像
Long
s 正在被处置。我可以用
future
为此,但我被它的异常吞咽行为咬了很多次,我很犹豫。为什么会这样?为什么在新线程中运行它会导致 GC 发疯,同时数字没有被释放?
谁能解释这种行为?
最佳答案
同步版本似乎正在处理 16M+ 结果,并且由于本地清除而不会保留结果序列的头部。这意味着随着您的进行,值被创建、处理和 GC 处理。
异步结束 results
在 fn 中,将保持头部,将所有 16M+ 值保留在内存中,可能导致 GC 颠簸?
我实际上无法重现您所描述的内容 - 同步和异步对我来说花费的时间与上面所写的大致相同。 (Clojure 1.9,Java 1.8)。
关于multithreading - 为什么在另一个线程中循环大量数据会导致GC过度活跃,并阻止一些数据被释放?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51776663/