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

前言

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

  • QObject、QThread、QRunnable、QThreadPool

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


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


知识点📖📖

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

基础概念

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

05-07 15:37