【问题标题】:Memory leak when embedding and updating a matplotlib graph in a PyQt GUI在 PyQt GUI 中嵌入和更新 matplotlib 图时发生内存泄漏
【发布时间】:2015-04-21 08:04:51
【问题描述】:

我正在尝试将每秒更新的 matplotlib 图形嵌入到 PyQt GUI 主窗口中。

在我的程序中,我通过如下所示的timer 函数使用threading.Timer 每秒调用一个更新函数。我有一个问题:我的程序每秒都会变大 - 每 4 秒大约 1k。我最初的想法是 append 函数(在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()

编辑:我不能发布我的整个代码,因为它需要很多 .dll 包含。所以我会试着解释一下这个程序是做什么的。

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

编辑 2: 在一些 discussion in chat 之后,我尝试将对 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 次左右迭代后崩溃。

【问题讨论】:

  • 您能否制作一个我们可以实际运行和验证的简单示例?例如,通过删除对附加函数的调用并且不使用计时器,s.t.它消耗内存真的很快吗?
  • 您打算通过np.delete 调用实现什么目标? Docs 说,它删除了子数组。所以看起来你的数组增长速度快于缩小速度。
  • @moooeeeep 我编辑了我的问题,这样更好吗?
  • 我认为你需要展示你的绘图是如何工作的。我怀疑每次调用 self.draw() 时,都会保留对数组和其他(可能更大)对象的引用。
  • 更简洁 - 我想知道您的 GUI 是否是问题所在。尝试注释掉self.draw() 和/或self.axes.plot(...,看看是否还有内存泄漏。如果没有,您就知道问题出在哪里了。

标签: python python-3.x matplotlib


【解决方案1】:

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

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

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

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

在这种情况下,由于您的主窗口由PyQt4 控制,您希望使用QTimer(参见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 的摘要,阐明了问题并解决了几种可能的解决方案。特别是 - OP 设法在此处模拟了一个更简单的可运行示例:http://pastebin.com/RXya6Zah

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

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

【讨论】:

    【解决方案2】:

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

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

    pylab.close() 
    

    How can I release memory after creating matplotlib figures

    【讨论】:

    • 是的 - 那是我和我的 cmets 一起去 OP 的地方,但不清楚他们是否使用 matplotlib。似乎注释掉所有的绘图并没有解决问题。
    猜你喜欢
    • 2017-04-20
    • 2010-11-17
    • 1970-01-01
    • 2013-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-26
    • 2022-01-14
    相关资源
    最近更新 更多