PySide6/PyQT多线程之 生命周期:从创建到销毁的完整解析

news2024/9/25 11:19:27

在这里插入图片描述

前言

PySide6/PyQT 中使用多线程时,多线程生命周期是一个重要的概念。如果不能正确地管理多线程生命周期,可能会导致程序崩溃、内存泄漏等问题。

在前面的文章中有介绍到 PySide6/PyQT 可以实现多线程的多个类,

  • QObject、QThread、QRunnable、QThreadPool

在本篇文章中,着力于介绍多线程的生命周期,而不是实现多线程,所以这里我选择了QThread 来做介绍。


一般来说,我们不用过于关注PySide6/PyQT多线程的生命周期,但是我们需要了解这些概念。


知识点📖📖

本文用到的几个PySide6/PyQT的知识点及链接。

作用链接
创建新线程QThread
对象间通信的机制,允许对象发送和接收信号Signal
用于响应Signal信号的方法Slot

基础概念

这里使用QThread示例。

PySide6/PyQT 多线程的生命周期,有以下六个步骤:

  1. 创建线程:使用 QThread 类创建线程实例。
  2. 启动线程:调用 start() 方法启动线程,线程会自动调用 run() 方法开始执行。
  3. 线程运行:在线程执行期间,可以在 run() 方法中执行需要在子线程中进行的耗时操作。
  4. 线程等待:在需要等待线程完成时,可以使用 wait() 方法来阻塞当前线程,直到线程执行完毕。
  5. 线程完成:在线程执行完毕后,会自动调用 finished() 信号,表示线程已经完成。
  6. 销毁线程:当线程执行完毕后,可以通过 deleteLater() 方法或者 quit() 方法来销毁线程实例。

1.创建线程

  • 重写run()方法,该方法会在新线程中执行
class MyThread(QThread):
    def __init__(self, parent=None):
        super().__init__(parent)
        
    def run(self):
        # 在新线程中执行的代码

2.启动线程

  • 实例化并使用start()启动线程
my_thread = MyThread()
my_thread.start()

3.线程运行

class MyThread(QThread):
    def __init__(self, parent=None):
        super().__init__(parent)
        
    def run(self):
        # 在新线程中执行的代码
        for i in range(10):
            print("Hello from thread:", self.currentThreadId())
            time.sleep(1)

            
my_thread = MyThread()
my_thread.start()
# 打印查看线程是否在运行
print(my_thread.isRunning())

4.线程等待

my_thread = MyThread()
my_thread.start()

# 线程等待,会阻塞当前线程
my_thread.wait()

5.线程完成

my_thread = MyThread()
my_thread.start()

# 打印查看线程是否完成
print(my_thread.isFinished())

6.销毁线程

class MyThread(QThread):
    def __init__(self, parent=None):
        super().__init__(parent)
        
    def run(self):
        # 在新线程中执行的代码
        for i in range(10):
            print("Hello from thread:", self.currentThreadId())
            time.sleep(1)
            
    def stop(self):
        self.quit()
        # 或者使用
        self.deleteLater()
        
    def __del__(self):
        self.wait()

高效管理生命周期

合理管理多线程的生命周期可以避免多线程资源的浪费和泄漏,以及更高效的利用系统资源。

在多线程生命周期中,QThreadQThreadPool+QRunnable的差异主要体现在线程的创建和销毁方式。

QThread

  • 需要手动创建和管理线程;
  • 每次调用QThreadstart()方法都会创建一个新的线程;
  • 当线程完成任务后,需要手动调用QThreadquit()deleteLater()方法来销毁线程

QThreadPool + QRunnable

  • 线程的创建和销毁都由QThreadPool来管理;
  • 通过将任务(即实现QRunnable的类)提交给QThreadPool来启动线程,并可以使用QThreadPool的方法来管理线程池中的线程;
  • 线程完成任务后,QThreadPool将自动销毁线程。

所以我推荐使用 QRunnable + QThreadPool 创建多线程,它们是一种更加方便和更友好的实现方式:

  • 更加方便地管理多线程的生命周期;
  • 减少手动管理线程的工作;
  • 有助于避免一些潜在的线程管理问题以及更有效地使用系统资源。

当然,在后面我也还会介绍关于 QThreadPool + QRunnable 的文章。

小技巧

如果需要重复执行相同的任务,我建议只创建一个Thread对象,并在使用时重复调用它的start() 方法。好处如下:

  • 减少系统资源占用:每次创建一个新的线程对象都需要重新的系统资源,而重复使用已有的线程对象可以减少系统资源占用;
  • 提高应用程序的响应速度:每次创建一个新的线程对象都需要一定的时间来分配和初始化资源,而重复使用已有的线程对象则可以减少线程创建和初始化的时间,从而提高应用程序的响应速度;
  • 方便管理线程状态:如果每次创建一个新的线程对象,那么就需要手动管理每个线程的状态,包括启动、暂停、继续、停止等。而如果重复使用已有的线程对象,则可以更方便地管理线程的状态。

示例伪代码如下:

from PySide6.QtCore import QThread

class MyThread(QThread):
    def __init__(self):
        super().__init__()

    def run(self):
        # 执行任务的代码

if __name__ == '__main__':
    thread = MyThread()
    thread.start()  # 第一次执行任务
    thread.wait()   # 等待线程完成

    thread.start()  # 第二次执行任务
    thread.wait()   # 等待线程完成

    thread.start()  # 第三次执行任务
    thread.wait()   # 等待线程完成

    # ...

实现

代码示例

# -*- coding: utf-8 -*-

import sys
from PySide6.QtCore import (QThread, Signal, Qt)
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget)


class Worker(QThread):
    finished = Signal()

    def __init__(self):
        super().__init__()

    def run(self):
        print("Thread started")
        for i in range(2):
            print(f"Thread running {i}")
            self.msleep(1000)
        print("Thread finished")
        self.finished.emit()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setup_ui()
        self.thread = None

    def setup_ui(self):
        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)

        self.label = QLabel("Ready", self)
        self.label.setAlignment(Qt.AlignCenter)

        self.button = QPushButton("Start Thread", self)
        self.button.clicked.connect(self.start_thread)

        layout = QVBoxLayout(central_widget)
        layout.addWidget(self.label)
        layout.addWidget(self.button)
        self.show()

    def start_thread(self):
        if not self.thread or not self.thread.isRunning():
            self.thread = Worker()
            self.thread.finished.connect(self.thread_finished)
            self.thread.start()
            self.label.setText("Running")
            self.thread.wait()

    def thread_finished(self):
        self.label.setText("Finished")
        self.thread.deleteLater()


if __name__ == "__main__":
    app = QApplication()
    window = MainWindow()
    sys.exit(app.exec())

这个案例完整地展示了 PySide6/PyQT 中多线程的生命周期管理,包括线程的创建、启动、运行、等待、结束以及线程对象的销毁。具体体现在以下几个方面:

  1. 创建线程对象:代码中通过创建 Worker 类来继承 QThread 类,并在 Worker 类中重写 run() 方法实现多线程执行的逻辑;
  2. 启动线程:在主窗口类 MainWindow 中的 start_thread() 方法中,通过创建 Worker 类的实例并调用 start() 方法来启动新的线程;
  3. 运行线程:在 run() 方法中,通过使用 msleep() 方法来模拟耗时操作;
  4. 线程等待:在start_thread()方法中使用了 self.thread.wait()阻塞当前线程;
  5. 结束线程:在 Worker 类的 run() 方法中,当完成耗时操作后,会发送一个 finished 信号,然后线程结束。主窗口类中,可以通过 finished 信号的连接函数 thread_finished() 来在线程结束时进行一些处理;
  6. 销毁线程对象:在 thread_finished() 方法中,通过调用 deleteLater() 方法来销毁线程对象,从而保证了线程对象的正确释放。

总结✨✨

一般来说,我们不用过于关注PySide6/PyQT多线程的生命周期,但是了解多线程生命周期的概念对于正确地使用多线程还是很有必要的。
因为理解线程的生命周期可以帮助我们更好地掌握线程的创建、启动、执行、阻塞、终止等过程,可以更加准确地编写 PySide6/PyQT 多线程。

后话

本次分享到此结束,
see you~~🐱‍🏍🐱‍🏍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++刷题笔记】继承和多态常见面试题汇总

对C继承和多态方面的部分面试题进行了汇总 一、概念考察 1. 下面哪种面向对象的方法可以让你变得富有( ) A: 继承 B: 封装 C: 多态 D: 抽象 2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体…

双目测距--4 双目立体匹配 获取深度图

在这之前需要已经完成双目标定,这里是利用双目标定结果利用SGBM算法获取深度图,以及转伪彩图。 目录 StereoSGBM用到的参数: 一、 预处理参数 二 、代价参数 三 、动态规划参数 四、后处理参数 reprojectImageTo3D函数 获取真实距离 …

ChatGPT - 快速生成 流程图

文章目录 Prompt输出Copy 到 drawio Prompt 我想做一个研发标准化的流程,但是我是一个小白,不懂研发管理的流 程,我希望你作为一个经验丰富的技术管理人员,请帮我梳理一个完整流程,包括需求分析、概要设计,代码走查等等,输出的节点不少于18个,包含逻辑判断的分支,要通循实事求…

【SpringCloud微服务实践】服务注册与发现

注册与发现 在之前的示例中,采取的是硬编码的方式,需要调用的微服务的地址是被我们写死在文件或代码中的。在传统应用程序中,一般都是这么做的,然而这种方式存在不少缺陷: 静态配置:因为是写死的网络地址…

力扣sql中等篇练习(十五)

力扣sql中等篇练习(十五) 1 页面推荐 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # ①找到1所对应的朋友 ②找到其朋友喜欢的页面 ③删选掉自己喜欢的页面 # 可能朋友中存在喜欢同样的界面 SELECT distinct page_id reco…

Java每日一练(20230507) 组合总和、缺失的正数、单词搜索II

目录 1. 组合总和 🌟🌟 2. 缺失的第一个正数 🌟🌟🌟 3. 单词搜索 II 🌟🌟🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/…

数字化转型导师坚鹏:企业干部如何进行数字化转型

企业干部如何进行数字化转型 ——数字化转型背景下重塑企业干部核心竞争力 授课背景: 很多银行存在以下问题: 企业干部不知道如何进行数字化转型? 企业干部不清楚银行数字化能力模型的内涵? 企业干部不知道如何通过数字化…

总结843

学习目标: 5月(张宇强化18讲,背诵25篇短文,熟词僻义300词基础词) 每日必复习(5分钟) 做记录本上3道题 学习内容: 暴力英语:回环诵读,继续背一篇阅读理解&…

前端实战项目:网易云静态页面——轮播图

前言 目前这个前端项目持续更新中~ 网易云静态页面——导航栏 Flex布局 文章目录 前言实现目标静态实现页面大致样子添加模糊的背景图添加图片下载客户端部分轮播图小圆点第一个小圆点变成红色以及当鼠标放上其他任一小圆点也变成红色左右按钮总代码 动态实现 实现目标 建立相…

Shift_RAM ip核的使用——ROM调用mif文件、传至Shift_RAM输出

Altera_Shift Register(RAM-based)ip核 前言1.创建mif文件,通过ROM ip核调用该mif文件1.1创建mif文件1.2顺序填充mif文件1.3创建ROM ip核调用mif文件 2.计数器读取mif文件中的数据2.1写一个0-15的循环计数器2.2实例化ROM ip核、调用计数器模块…

【C++学习】类和对象--多态

多态的基本语法 多态是C面向对象三大特性之一 静态多态: 函数重载和运算符重载属于静态多态,复用函数名(函数地址早绑定,编译阶段确定函数地址) 动态多态: 派生类和虚函数实现运行时多态(函数地…

Python |浅谈爬虫的由来

本文概要 本篇文章主要介绍Python爬虫的由来以及过程,适合刚入门爬虫的同学,文中描述和代码示例很详细,干货满满,感兴趣的小伙伴快来一起学习吧! 🌟🌟🌟个人简介🌟&…

快速搭建一个spring入门案例及整合日志

目录 环境要求 构建模块 程序开发 引入依赖 创建java类 创建配置文件 创建测试类测试 运行测试程序 程序分析 spring中配置启用Log4j2日志框架 Log4j2日志概述 引入Log4j2依赖 加入日志配置文件 测试 使用日志 环境要求 JDK:Java17(Spring6要…

Linux高性能服务器编程|阅读笔记:第6章 - 高级I/O函数

目录 简介6.1 pipe函数6.2 dup函数和dup2函数6.3 readv函数和writev函数6.4 sendfile函数6.5 mmap函数和munmap函数6.6 splice函数6.7 tee函数6.8 fcntl函数结语 简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出&…

10-Vue技术栈之脚手架配置代理(解决跨域问题)+ GitHub用户搜索案例

目录 1、基本使用1.1 方法一1.2 方法二 2、GitHub用户搜索案例 1、基本使用 1.1 方法一 ​ 在vue.config.js中添加如下配置: devServer:{proxy:"http://localhost:5000" }说明: 优点:配置简单,请求资源时直接发给前…

用三角函数解决几何问题

如图,在 △ A B C \triangle ABC △ABC 中, A C > 5 , A B > A C AC>5,AB>AC AC>5,AB>AC,点 E E E 是 A B AB AB 上一点,链接 C E CE CE,将 △ B C E \triangle BCE △BCE 沿 C E CE CE 折叠&…

【unity之数据持久化】-Unity公共类PlayerPrefs

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

FreeRTOS 内存管理

文章目录 一、FreeRTOS 内存管理简介二、 内存碎片三、heap_1 内存分配方法1. 分配方法简介2. 内存申请函数详解3. 内存释放函数详解 四、heap_2 内存分配方法1. 分配方法简介2. 内存块详解3. 内存堆初始化函数详解4. 内存块插入函数详解5. 内存申请函数详解6. 内存释放函数详解…

操作系统考试复习——第四章 4.3连续分配存储管理方式

在这里的开头需要讲述一下碎片,碎片分为内碎片和外碎片两种。 内碎片:分区之内未被利用的空间外碎片:分区之间难以利用的空闲分区(通常是小空闲分区)。 连续分配存储管理方式: 为了能将用户程序装入内存&#xff0c…

力扣刷题Day12_2

144.二叉树的前序遍历 测试代码main() class TreeNode:def __init__(self, valNone, leftNone, rightNone):self.val valself.left leftself.right rightfrom typing import Listclass Solution:def preorderTraversal(self, root: TreeNode) -> List[int]:s Solution…