在PyQt GUI中嵌入和更新matplotlib图表时出现内存泄漏问题

5
我正在尝试将每秒更新的matplotlib图表嵌入到PyQt GUI主窗口中。在我的程序中,我使用threading.Timer通过下面的timer函数每秒调用一次更新函数。我有一个问题:我的程序每秒都会增加约1k,大约4秒钟。我的初步想法是,update_figure函数中返回一个新数组的append函数没有删除旧数组?这可能是我的问题的原因吗?请注意,保留HTML标记。
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()

编辑:我无法发布完整的代码,因为它需要很多.dll文件。所以我会尽力解释这个程序的作用。

在我的GUI中,我想展示我的CO2值随时间变化的情况。我的get_co22函数仅返回浮点数值,我非常确定它可以正常工作。使用上面显示的计时器,我想将一个值附加到一个matplotlib图形中 - Axes对象对我可用,像这样:self.axes。我试着绘制数据的最后10个值。

编辑2:经过一些聊天讨论,我尝试将对update_figure()的调用放在一个while循环中,并使用一个线程调用它,并成功地创建了这个最小示例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个循环后会崩溃。


3
你能否举一个我们实际可以运行和验证的简单示例呢?例如,删除对其他函数的调用并去掉计时器,并使其快速消耗内存。 - moooeeeep
你使用 np.delete 调用的目的是什么?文档 表明,它可以删除子数组。因此,似乎你的数组增长速度比缩小速度快。 - Gill Bates
@moooeeeep,我编辑了我的问题,这样好一些吗? - Rowan Klein Gunnewiek
1
更简洁地说,我想知道您的GUI是否存在问题。尝试将 self.draw()self.axes.plot(... 注释掉,看看是否仍然存在内存泄漏。如果没有 - 您就有了一个线索,可以确定问题出在哪里。 - J Richard Snape
@JRichardSnape 我尝试注释掉self.draw()和self.axes.plot()。程序仍在增长,但增长速度比以前慢了! - Rowan Klein Gunnewiek
显示剩余6条评论
2个回答

2
问题显然不在于您如何将数据添加到numpy数组中或截断它。
问题在于您的线程模型。将计算循环与GUI控制循环集成在一起是困难的。
从根本上讲,您需要让GUI线程控制何时调用更新代码(必要时生成新线程来处理它),以便
  1. 您的代码不会阻止GUI更新,
  2. GUI更新不会阻止您的代码执行,并且
  3. 您不会生成大量线程来持有多个对象的副本(这可能是您的内存泄漏来源)。
在这种情况下,由于您的主窗口由 PyQt4控制,因此您需要使用一个 QTimer (请参见此处的简单示例
因此,请修改您的 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)

这应该可以工作。保持对的引用是至关重要的,因此使用self.t = QtCore.QTimer()而不是t = QtCore.QTimer(),否则对象将被垃圾回收。

注意:

这是一个聊天记录中长串讨论的摘要,澄清了问题并讨论了多种可能的解决方案。特别地,OP在这里提供了一个更简单的可运行示例:http://pastebin.com/RXya6Zah

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

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


0

是的 - 那就是我对原帖作者的评论的意思,但不清楚他们是否正在使用matplotlib。此外,注释掉所有绘图似乎并没有解决问题。 - J Richard Snape

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