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

举报
frica01 发表于 2023/10/31 20:58:32 2023/10/31
【摘要】 一般来说,我们不用过于关注多线程的生命周期,但是了解多线程生命周期的概念对于正确地使用多线程还是很有必要的。因为理解线程的生命周期可以帮助我们更好地掌握线程的创建、启动、执行、阻塞、终止等过程,可以更加准确地编写多线程。

前言

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~~🐱‍🏍🐱‍🏍

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。