PyQt5桌面应用开发(3):并行设计

PyQt5桌面应用并行设计:QThreadPool与QRunnable在IO与CPU任务中的应用
本文详细介绍了如何在PyQt5桌面应用中处理并行运行的需求,特别是针对IO密集型和CPU密集型任务。使用QThreadPool和QRunnable可以有效地进行线程管理和任务执行。对于IO密集型任务,多线程能有效提升性能;而对于CPU密集型任务,推荐使用进程以绕过GIL限制。文章还提供了一个并行库的实现示例,展示了如何在实际应用中结合线程池和进程来处理不同类型的并行任务。

PyQt5桌面应用系列

前言

对一个桌面应用程序来说,有一个无法回避的主题就是并行设计。其主要的原因就是在计算机读取数据或者运行耗时计算时,用户界面必须避免“卡死”的状态。粗略分析,有两种情况。第一种情况是跟IO相关的操作。第二种情况是要消耗大量CPU计算的操作。

并行运行需求:IO vs. CPU

IO密集型

IO密集型的情况下,程序花事先无法确定的时间等待IO返回的数据。对于网络通信、文件接收、传感器的数据采集,底层采取定时或者是其他连接方式,最终数据是通过数据集合(文件块、数据包)来得到的。

在这种情况下,即使Python里面具有GIL的限制,多线程也能够非常有效地解决。如果是大批量数据通道,还有一些基于协程的方式。

在PyQt5中,可以通过线程池来解决。事先准备一个线程池,启动IO密集型的线程,通过信号与槽来处理交互。

大概来说:调用线程池和IO线程的主程序,只需要处理:

  1. 错误
  2. 完成
  3. 数据
  4. 进度
    这么几个信号就能够完成任务。

如果需要在IO的同时实时来把数据更新在界面上,那么对进度信号略做改变就能够完成。

CPU密集型

计算或者是CPU密集型的任务,由于Python的限制(GIL),采用线程没有一点的帮助。

那么就应该采取进程的方法,并通过管道来传递数据。这里就有一个设计选择。可以通过PyQt5的进程,也可以通过Python的进程。

这里直接说结论:

  1. PyQt5的进程需要自己处理Python运算代码的传输、数据传输管道,相当复杂;
  2. Python提供了良好的进程并行支持,但是不支持传输PyQt5中QObject的子类。

因此,我这里提供一个思路。具体计算(Python代码的函数)的实现放到一个Python提供的进程中完成,返回数据通过队列自动处理的管道完成,这就把CPU计算封装为一个IO(对PyQt5主程序而言)。

与前面的IO密集型任务一样,采用线程池和Qt线程完成。

并行运行设计

PyQt的核心并行机制:QThreadPoolQRunnable

核心的对象和关系如图,主程序启动QThreadPool,里面运行的都是QRunnable的子类,称为Worker。这里的Worker分为进程和线程两类。进程类封装一个Python进程。两个都通过信号与槽跟PyQt5主程序交互。
在这里插入图片描述

QThreadPool的方法如下图,通过start函数来启动QRunnable对象。还可以管理最大线程数。
在这里插入图片描述
QRunnable的run函数就是线程执行的主要操作。这里设计的两个子类,构造函数都是一个函数对象和这个函数的参数、命名参数。
在这里插入图片描述
而交互的信号也可以专门定义一个类WorkerSignals.
在这里插入图片描述

CPU密集型进程

对于CPU密集型的进程,专门设计一个WorkerProcess类,来进行同样的封装,为一个值得注意的是,创建这个类的QProcessWorker必须维持一个队列,也就是下图中构造函数中的第三个参数:multiple_process_queue。
在这里插入图片描述
完成设计之后,就可以将前面的采集音频的程序做亿点点改造。可以选择用进程也可以选择用线程。二者都能够保证采集和播放的过程大体不影响界面的响应(操作图片、变更频域和时域)。值得注意的是,进程的方式,启动时间会大大高于线程。

代码

PyQt5桌面应用并行库

这部分代码用户无需修改,定义了信号类(必须是QObject的子类)、进程类、线程任务、进程任务。

import multiprocessing as mp
import sys

from PyQt5.QtCore import QObject, pyqtSignal, QRunnable, pyqtSlot


class WorkerSignals(QObject):
    """
    Defines the signals available from a running worker thread.

    Supported signals are:

    finished
        No data

    error
        `tuple` (exctype, value, traceback.format_exc() )

    result
        `object` data returned from processing, anything

    progress
        `int` indicating % progress

    """
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(int)


class QtThreadWorker(QRunnable):
    """
    Worker thread

    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function
    """

    def __init__(self, fn, *args, **kwargs):
        super(QtThreadWorker, self).__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals: WorkerSignals = WorkerSignals()

    @pyqtSlot()
    def run(self):
        """
        Initialise the runner function with passed args, kwargs.
        """

        # Retrieve args/kwargs here; and fire processing using them
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            import traceback
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大福是小强

除非你钱多烧得慌……

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值