在 PyQt GUI 中嵌入和更新 matplotlib 图形时发生内存泄漏

Memory leak when embedding and updating a matplotlib graph in a PyQt GUI

我正在尝试将每秒更新一次的 matplotlib 图嵌入到 PyQt GUI 主程序中 window。

在我的程序中,我通过如下所示的 timer 函数使用 threading.Timer 每秒调用一个更新函数。我有一个问题:我的程序每秒都在变大——以每 4 秒大约 1k 的速度增长。我最初的想法是追加函数(returns update_figure 中的新数组)不会删除旧数组?这可能是我的问题的原因吗?

def update_figure(self):
    self.yAxis = np.append(self.yAxis, (getCO22()))
    self.xAxis = np.append(self.xAxis, self.i)
    # print(self.xAxis)
    if len(self.yAxis) > 10:
        self.yAxis = np.delete(self.yAxis, 0)

    if len(self.xAxis) > 10:
        self.xAxis = np.delete(self.xAxis, 0)

    self.axes.plot(self.xAxis, self.yAxis, scaley=False)
    self.axes.grid(True)

    self.i = self.i + 1

    self.draw()

这是我的计时器函数 - 这是通过单击我的 PyQt GUI 中的按钮触发的,然后如您所见调用自身:

def timer(self):
    getCH4()
    getCO2()
    getConnectedDevices()
    self.dc.update_figure()
    t = threading.Timer(1.0, self.timer)
    t.start()

编辑: 我不能 post 我的整个代码,因为它需要大量的 .dll 包含。所以我将尝试解释这个程序的作用。

在我的 GUI 中,我想随时间显示我的 CO2 值。我的 get_co22 函数只是 returns 一个浮点值,我 100% 确定它可以正常工作。使用我的计时器,如上所示,我想将一个值附加到 matplotlib 图形 - Axes 对象对我可用 self.axes。我尝试绘制数据的最后 10 个值。

编辑 2: 经过一些 discussion in chat, I tried putting the call to update_figure() in a while loop and using just one thread to call it and was able to make this minimal example http://pastebin.com/RXya6Zah。这将调用 update_figure() 的代码结构更改为以下内容:

def task(self):
    while True:
        ui.dc.update_figure()
        time.sleep(1.0)

def timer(self):
    t = Timer(1.0, self.task())
    t.start()

但是现在程序在大约 5 次迭代后崩溃了。

如果您每次都创建一个新图形,这很常见。

matplotlib 不会释放您创建的图形,除非您提出要求,例如:

pylab.close() 

How can I release memory after creating matplotlib figures

问题绝对不在于您如何追加或截断 numpy 数组。

这里的问题出在你的线程模型上。将计算循环与 GUI 控制循环集成起来很困难。

从根本上说,您需要 GUI 线程来控制何时调用更新代码(必要时生成一个新线程来处理它)- 这样

  1. 您的代码不会阻止 GUI 更新,
  2. GUI 更新不会阻止您的代码执行并且
  3. 您不会生成大量持有多个对象副本的线程(这可能是内存泄漏的来源)。

在这种情况下,因为您的主要 window 由 PyQt4, you want to use a QTimer (see a simple example here)

控制

所以 - 将您的 timer 代码更改为

def task(self):
    getCH4()
    getCO2()
    getConnectedDevices()
    self.dc.update_figure()

def timer(self):
    self.t = QtCore.QTimer()
    self.t.timeout.connect(self.task)
    self.t.start(1000)

这应该有效。保留对 QTimer 的引用是必不可少的 - 因此 self.t = QtCore.QTimer() 而不是 t = QtCore.QTimer(),否则 QTimer 对象将被垃圾收集。


注:

这是long thread in chat clarifying the issue and working through several possible solutions. In particular - the OP managed to mock up a simpler runnable example here: http://pastebin.com/RXya6Zah

的摘要

完整可运行示例的固定版本在这里:http://pastebin.com/gv7Cmapr

相关代码和解释在上面,但链接可能会帮助任何想要复制/解决问题的人。请注意,它们需要安装 PyQt4