在 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 线程来控制何时调用更新代码(必要时生成一个新线程来处理它)- 这样
- 您的代码不会阻止 GUI 更新,
- GUI 更新不会阻止您的代码执行并且
- 您不会生成大量持有多个对象副本的线程(这可能是内存泄漏的来源)。
在这种情况下,因为您的主要 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
我正在尝试将每秒更新一次的 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 线程来控制何时调用更新代码(必要时生成一个新线程来处理它)- 这样
- 您的代码不会阻止 GUI 更新,
- GUI 更新不会阻止您的代码执行并且
- 您不会生成大量持有多个对象副本的线程(这可能是内存泄漏的来源)。
在这种情况下,因为您的主要 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