pyqt+matplotlib多进程图像刷新

文章目录

测试准备

在 PyQt 中使用多进程比多线程复杂,因为多进程之间没有共享内存,不能用Qt的信号槽通信。但采用Python的multiprocessing和queue,其实也没复杂到拿去。

为了体现出多进程工作的特点,接下来实现如下功能,即4个进程分别生成数据,分别对应图中的一条线。

本测试与pyqt+matplotlib多线程绘图采用了相同的ui,由于实现异步的方式不同,故而import的模块稍有差异

python 复制代码
import numpy as np
import multiprocessing as mp
from PyQt6.QtCore import QTimer
import sys
import numpy as np
from PyQt6 import uic
from PyQt6.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QWidget
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar

测试函数

为pyqt的多进程绘图软件编写如下测试函数,三个输入参数,分别是任务ID,多进程消息队列以及绘图点数。

python 复制代码
def heavy_compute(task_id, q, N):
    xs, ys = [], []
    freq = (task_id + 1) * 0.5 

    for i in range(N):
        arr = np.random.rand(50000)
        res = np.sum(np.sin(arr * freq) * np.cos(arr))
        
        xs.append(i)
        ys.append(float(res))        
        if i % 100 == 0:
            q.put((task_id, xs.copy(), ys.copy()))
    q.put(("done", task_id))

主窗口

主窗口代码如下。和基于QThread与pyqtSignal的多线程方案相比,主要区别在于采用了mp.Queue()作为进程间数据传输的工具。由于缺少pyqtSignal这样便捷的事件触发绑定工具,主窗口在绘图时采用了Timer轮询的方案,即不断查询queue是否返回了数据,如果返回,就画图,否则继续循环。

python 复制代码
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi("thTest.ui", self)

        self.toolbar_container = self.findChild(QWidget, "toolbar_container")
        layout = self.toolbar_container.layout()
        if layout is None:
            layout = QHBoxLayout(self.toolbar_container)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.toolbar = NavigationToolbar(self.mpl_canvas, self)
        layout.addWidget(self.toolbar)
        
        self.NUM_TASKS = 4  # 要同时运行的进程数
        self.STEPS = 1000   # 每个进程的计算总步数
        self.colors = ['#e6194b', '#3cb44b', '#4363d8', '#f58231'] # 4种颜色
        
        self.isRunning = False
        self.processes = []
        self.queue = mp.Queue()
        self.finished_count = 0
        
        self.timer = QTimer()
        self.timer.timeout.connect(self._poll_queue)

        self.ax = self.mpl_canvas.axes
        self.ax.clear()
        
        self.lines = {}
        for i in range(self.NUM_TASKS):
            line, = self.ax.plot([], [], color=self.colors[i], linewidth=2, label=f'Process {i}')
            self.lines[i] = line
            
        self.ax.legend(loc='upper left')
        self.ax.set_title("Multiprocessing Pool: Real-time Multi-Curve")
        self.mpl_canvas.draw()

        self.pbDraw.clicked.connect(self._toggle_task)

    def _toggle_task(self):
        if not self.isRunning:
            self._start_pool()
        else:
            self._stop_pool()

    def _start_pool(self):
        self.isRunning = False
        self.finished_count = 0
        self.pbDraw.setText("停止/等待完成")

        self.processes = []
        for i in range(self.NUM_TASKS):
            p = mp.Process(target=heavy_compute, args=(i, self.queue, self.STEPS), daemon=True)
            p.start()
            self.processes.append(p)

        self.isRunning = True
        self.timer.start(50)

    def _stop_pool(self):
        self.pbDraw.setText("正在等待子进程结束...")
        self.pbDraw.setEnabled(False)

    def _poll_queue(self):
        has_update = False
        while True:
            try:
                msg = self.queue.get_nowait() 
            except Exception:
                break # 队列空了,退出本次轮询
                
            if msg[0] == "done":
                self.finished_count += 1
                if self.finished_count >= self.NUM_TASKS:
                    self._on_all_finished()
            else:
                # 正常的数据更新
                task_id, x, y = msg
                # 根据 task_id 找到对应的线条并更新数据
                self.lines[task_id].set_data(x, y)
                has_update = True

        # 只要有新数据,就刷新一次画布 (避免频繁刷新)
        if has_update:
            self.ax.relim()
            self.ax.autoscale_view()
            self.mpl_canvas.draw_idle()

    def _on_all_finished(self):
        self.timer.stop()
        for p in self.processes:
            p.join()  # 等待所有子进程结束
        self.isRunning = False
        self.pbDraw.setEnabled(True)
        self.pbDraw.setText("启动计算")
        print(">>> 所有进程池任务已执行完毕!")
相关推荐
for_ever_love__2 天前
Matplotlib——把训练曲线和分布画出来
matplotlib·数据可视化·大模型开发·训练曲线
多弗朗皮卡丘8 天前
C++ stack和queue
开发语言·c++·deque·queue·stack·容器适配器
茶栀(*´I`*)8 天前
【Python数据可视化】Matplotlib从零到精通:图表核心解剖、多子图布局与三大经典图表实战
python·信息可视化·matplotlib
阿钱真强道9 天前
20 嵌入式操作系统 | ubus:把自己的程序状态暴露出去
c语言·进程通信·ubus·ubusd·blobmsg
zhanghaha131410 天前
Python进阶教程:35_PyQt (保姆级零基础教程)
开发语言·python·pyqt
重生之小比特17 天前
【初阶C++】stack和queue
开发语言·c++·vs·queue
线上放牧人18 天前
Windows删除图标缓存
windows·python·pyqt
jzshmyt19 天前
我用 Python 从零“生成“了一个宇宙,然后让它观察自己(v14)
人工智能·pytorch·python·numpy·matplotlib·空间计算·scipy
典典分享指南20 天前
用飞书 Wiki 搭内容作战室:从零搭建团队内容协作中枢
django·flask·numpy·pyqt
千里码aicood21 天前
pyqt-基于Qt的公共卫生间管理系统的设计与实现
pyqt