Skip to main content
 首页 » 编程设计

python之使用 Matplotlib、PyQt 和 Threading 进行实时绘图以 python 崩溃结束

2025年05月04日24birdshome

我一直在为我的 Python 应用程序苦苦挣扎,找不到任何答案。

我有一个使用 Matplotlib 小部件的 PyQT GUI 应用程序。 GUI 启动一个新线程来处理绘制到 mpl 小部件。恐怕我现在通过从导致崩溃的另一个线程访问 matplotlib 绘图组件来运行到竞争条件。

这基本上就是我的代码的样子:

class Analyzer(QMainWindow, Ui_MainWindow): 
  def __init__(self, parent=None): 
    self.timer = QTimer() 
    super(Analyzer, self).__init__(parent) 
    self.setupUi(self) 
 
    self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox) 
 
    self.plotQueue = Queue.Queue() 
    self.plotterStarted = False 
 
    self.plotter = Plotter(self.mpl, self.plotQueue) 
    self.cam = Cam(self.plotQueue, self.textEdit) 
    ... 
 
class Ui_MainWindow(object): 
  def setupUi(self, MainWindow): 
    ... 
    self.mpl = MplWidget(self.centralWidget) 
    ... 
 
class MplWidget(QtGui.QWidget): 
"""Widget defined in Qt Designer""" 
  def __init__(self, parent = None): 
    QtGui.QWidget.__init__(self, parent) 
    self.canvas = MplCanvas() 
    ... 
 
class MplCanvas(FigureCanvas): 
"""Class to represent the FigureCanvas widget""" 
  def __init__(self):         
    # setup Matplotlib Figure and Axis 
    self.fig = Figure() 
    self.ax = self.fig.add_subplot(111) 
 
    # initialization of the canvas 
    FigureCanvas.__init__(self, self.fig) 
 
    FigureCanvas.updateGeometry(self) 

和绘图仪类:

class Plotter(): 
  def __init__(self, mpl="", plotQueue=""): 
    self.mpl = mpl 
    self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox) 
    self.plotQueue = plotQueue 
    ... 
  def start(self): 
    threading.Thread(target=self.run).start() 
 
  ''' Real time plotting ''' 
  def run(self): 
    while True: 
      try: 
        inputData = self.plotQueue.get(timeout=1) 
 
        # Go through samples 
        for samples in inputData: 
            self.line, = self.mpl.canvas.ax.plot(x, y, animated=True, label='Jee') 
 
            for sample in samples: 
                x.append(sample['tick']) 
                y.append(sample['linear']) 
 
            self.line.set_data(x,y) 
            self.mpl.canvas.ax.draw_artist(self.line) 
            self.mpl.canvas.blit(self.mpl.canvas.ax.bbox) 
         ... 

所以我将 mpl 和 plotQueue 传递给 Plotter 类对象。 PlotQueue 填充在处理来自外部硬件的传入数据的 Cam 类中。 Plotter 读取 plotQueue,对其进行处理并为 mpl 调用绘图。

但这是访问 mpl 的线程安全方法吗?如果没有,我应该怎么做?对此的任何提示表示赞赏。


编辑 1.

我按照评论中的建议在主线程中添加了 QTimer 来处理绘图。经过一些小的调整后,我让它运行得相当好。

class Analyzer(...): 
  def __init__(self, parent=None): 
    QObject.connect(self.timer, SIGNAL("timeout()"), self.periodicCall) 
 
  def periodicCall(self): 
    self.plotter.draw() 
 
  def startButton(self): 
    self.timer.start(10) 

非常感谢有用的评论。

请您参考如下方法:

如果您程序中的 matplotlib 使用 QT 后端(我假设是因为您将其嵌入到 Qt 应用程序中),那么绘图将在您调用 matplotlib 命令的线程中完成。这将是一个问题,因为 Qt 要求所有绘图都从主线程完成。 所以我相当肯定你不能简单地修复它。(如果你使用的是 GTK,你可以使用 gtk 锁来防止主进程与 GUI 交互,而你从你的线程,但 Qt 摆脱了他们在 v4 及更高版本中的类似锁定)。

你有几个选择:

  1. 尝试分离出 matplotlib 的绘图部分(甚至不可能?)并通过使用 QApplication.postEvent() 发送事件让它们在主线程中运行

  2. 不使用线程,而是在主线程中使用回调(可能使用 QTimer 定期调用或在程序空闲时调用)。这可能不会影响您的应用程序的性能,因为 Python GIL 无论如何都会阻止真正的多线程行为。

  3. 使用不同的绘图库。我看了一下 PyQtGraph前几天,它似乎进展顺利。乍一看,我认为它有能力在幕后为你处理所有这一切,使用 RemoteGraphicsView .这将启动第二个进程来执行 CPU 密集型绘图工作,从而绕过上述 Python GIL 问题。如果您有兴趣,请查看他们提供的示例