如何在多线程中安全地使用 Matplotlib 进行实时绘图

455 阅读2分钟

在使用 Matplotlib 进行实时绘图时,如果在其他线程中访问 Matplotlib 的绘图组件,可能会导致程序崩溃。这是因为 Matplotlib 在 Qt 后端下,绘图操作必须在主线程中进行,而在其他线程中进行绘图操作会引发竞争条件,导致程序崩溃。

2. 解决方案

解决这个问题的常见方法有两种:

  • 方法一:使用定时器

可以使用定时器在主线程中定期调用绘图函数,从而避免在其他线程中访问 Matplotlib 组件。这种方法简单易行,但可能会导致绘图延迟。

  • 方法二:使用事件

可以使用 QApplication.postEvent() 方法将绘图事件发送到主线程,从而在主线程中进行绘图操作。这种方法可以避免绘图延迟,但可能需要对代码进行一些修改。

代码例子

方法一:使用定时器

import sys

from PyQt5 import QtCore, QtGui, QtWidgets
import matplotlib.pyplot as plt

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        # 创建 Matplotlib 窗口
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # 创建定时器
        self.timer = QtCore.QTimer()
        self.timer.setInterval(100)  # 100 毫秒
        self.timer.timeout.connect(self.update_plot)

        # 启动定时器
        self.timer.start()

    def update_plot(self):
        # 在主线程中更新绘图
        self.mpl_widget.update_plot()

class MplWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        # 创建 Matplotlib 图形
        self.figure, self.axes = plt.subplots()

        # 将 Matplotlib 图形嵌入到 QWidget 中
        self.canvas = FigureCanvas(self.figure)
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.canvas)
        self.setLayout(self.layout)

    def update_plot(self):
        # 更新绘图
        self.axes.cla()  # 清除绘图区域
        x = [1, 2, 3, 4, 5]
        y = [10, 20, 30, 40, 50]
        self.axes.plot(x, y)
        self.canvas.draw()  # 绘制图形

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

方法二:使用事件

import sys

from PyQt5 import QtCore, QtGui, QtWidgets
import matplotlib.pyplot as plt

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        # 创建 Matplotlib 窗口
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # 创建事件循环
        self.event_loop = QtCore.QEventLoop()

        # 创建定时器
        self.timer = QtCore.QTimer()
        self.timer.setInterval(100)  # 100 毫秒
        self.timer.timeout.connect(self.update_plot)

        # 启动定时器
        self.timer.start()

        # 启动事件循环
        self.event_loop.exec_()

    def update_plot(self):
        # 在主线程中更新绘图
        QtWidgets.QApplication.postEvent(self.mpl_widget, PlotEvent())

class PlotEvent(QtCore.QEvent):
    def __init__(self):
        super().__init__(QtCore.QEvent.User)

class MplWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        # 创建 Matplotlib 图形
        self.figure, self.axes = plt.subplots()

        # 将 Matplotlib 图形嵌入到 QWidget 中
        self.canvas = FigureCanvas(self.figure)
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.canvas)
        self.setLayout(self.layout)

    def event(self, event):
        if isinstance(event, PlotEvent):
            # 更新绘图
            self.axes.cla()  # 清除绘图区域
            x = [1, 2, 3, 4, 5]
            y = [10, 20, 30, 40, 50]
            self.axes.plot(x, y)
            self.canvas.draw()  # 绘制图形

            return True
        else:
            return super().event(event)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()