multithreading - 为什么在另一个线程中循环大量数据会导致GC过度活跃,并阻止一些数据被释放?

标签 multithreading concurrency clojure garbage-collection

我正在编写的代码采用了 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 使用异步版本时:

VisualVM Snapshot

同步版本显示峰值为 Long相比之下,s 一次约为 45mb。

CPU使用率也大不相同:

enter image description here

有大量的 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/

相关文章:

java - 将 clojure/scala repl 附加到正在运行的 JVM

c++ - 如何在c linux中通过服务器处理多个套接字客户端

c# - 使用双缓冲技术进行并发读写?

java - JVM 似乎很快就停止了上下文切换

interface - 在 Clojure 中实现一个带有可变数量 args 的 Java 接口(interface)方法

string - 来自 Vector 的逗号分隔字符串值

python3中的多线程

java - java连接两个线程

c# - Windows 服务中的线程中的计时器

concurrency - 没有 "read"引用的 clojure 引用集的语义是什么?