PySide6从0开始学习的笔记(十五) 线程管理

在 PySide6 中,GUI 线程(主线程)负责处理界面渲染、用户交互等操作,若在主线程中执行耗时操作(如网络请求、文件读写、复杂计算),会导致界面卡顿甚至无响应。线程管理的核心是将耗时操作放到子线程中执行,通过信号与槽机制实现线程间通信,避免阻塞主线程。

看一个实例:

python 复制代码
import sys
import time

from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QApplication

app = QtWidgets.QApplication(sys.argv)

class MyWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.label = QtWidgets.QLabel(self)
        self.pushButton = QtWidgets.QPushButton(self)
        self.pushButton.setText("空闲中...")
        self.pushButton.clicked.connect(self.on_pushButton_clicked)
        # 定时器
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.on_timer_timeout)
        self.timer.start(1000)
        self.on_timer_timeout()
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.pushButton)

    def on_timer_timeout(self):
        time_str = time.strftime("%M:%S", time.localtime())
        self.label.setText(time_str)


    def on_pushButton_clicked(self):
        self.pushButton.setText("业务忙...")
        QApplication.processEvents()  # 强制刷新GUI
        QTimer.singleShot(0, self.seconds_operation)   # 延迟执行耗时函数
        self.pushButton.setText("空闲中...")

    def seconds_operation(self, s=3):
        """
        耗时约输入的s秒钟的运算函数
        采用CPU密集型计算 + 时间校准,确保不同硬件下耗时稳定在s秒左右
        """
        start_time = time.perf_counter()  # 高精度计时
        target_duration = s  # 目标耗时(秒)
        x = 0.0
        # CPU密集型循环(浮点运算+整数运算结合,避免编译器优化)
        while time.perf_counter() - start_time < target_duration:
            # 混合运算增加CPU负载,避免空循环被优化
            for i in range(3000):
                x += (i ** 0.5) * (i % 17)
                x = x % 1000000  # 防止数值溢出

widget = MyWindow()
widget.show()
sys.exit(app.exec())

在这段代码中,当点击按钮就会执行一个长耗时任务,在长耗时任务执行期间,画面的更新停止响应了。原因是GUI的线程被长耗时任务阻塞,画面的更新(包括画面更新、操作响应)都会被延迟或停止执行,直到长耗时任务结束。

解决办法:将长耗时任务放到子线程,保持主线程的流畅运行和即时响应、更新。

python 复制代码
import sys
import time

from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import Signal, QThread

app = QtWidgets.QApplication(sys.argv)

class MyWindow(QtWidgets.QWidget):
    task_finished = Signal()  # 任务完成信号
    def __init__(self):
        super().__init__()
        self.label = QtWidgets.QLabel(self)
        self.pushButton = QtWidgets.QPushButton(self)
        self.pushButton.setText("空闲中...")

        # 定时器
        self.timer = QtCore.QTimer()  # 创建一个定时器
        self.timer.start(1000)   # 每秒钟触发一次

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.pushButton)

        self.worker = worker_thread()  # 创建工作线程
        self.signal_slot()  # 连接信号和槽函数

    def signal_slot(self):
        # 定时器触发时,更新时间显示
        def on_timer_timeout():
            time_str = time.strftime("%M:%S", time.localtime())
            self.label.setText(time_str)
        self.timer.timeout.connect(on_timer_timeout)

        # 按钮点击时,启动工作线程
        def on_pushButton_clicked():
            self.pushButton.setText("业务忙...")
            self.pushButton.setEnabled(False)
            self.worker.start()
        self.pushButton.clicked.connect(on_pushButton_clicked)

        # 工作线程完成时,恢复按钮状态
        def on_worker_finished():
            self.pushButton.setText("空闲中...")
            self.pushButton.setEnabled(True)
        self.worker.task_finished.connect(on_worker_finished)


# 定义工作线程
class worker_thread(QThread):
    task_finished = Signal()  # 任务完成信号
    def __init__(self):
        super().__init__()

    def run(self):   # 当线程启动时,执行此函数(QThread.start()是启动run()的唯一合法入口)
        self.seconds_operation()   # 执行耗时函数
        self.task_finished.emit()  # 发送完成信号

    def seconds_operation(self, s=3):
        """
        耗时约输入的s秒钟的运算函数
        采用CPU密集型计算 + 时间校准,确保不同硬件下耗时稳定在s秒左右
        """
        start_time = time.perf_counter()  # 高精度计时
        target_duration = s  # 目标耗时(秒)
        x = 0.0
        # CPU密集型循环(浮点运算+整数运算结合,避免编译器优化)
        while time.perf_counter() - start_time < target_duration:
            # 混合运算增加CPU负载,避免空循环被优化
            for i in range(3000):
                x += (i ** 0.5) * (i % 17)
                x = x % 1000000  # 防止数值溢出


widget = MyWindow()
widget.show()
sys.exit(app.exec())

下面开始学习PySide6 线程的基础知识。

一、PySide6 线程基础

1. 核心类

  • QThread:PySide6 中管理线程的核心类,每个 QThread 实例对应一个子线程实体。
  • QObject:所有 Qt 对象的基类,支持信号与槽机制,可将一些逻辑代码定义成一个QObject通过 moveToThread() 移到指定线程执行。
  • 信号(Signal)/ 槽(Slot):线程间安全通信的核心,避免直接操作跨线程对象。
  • 注意:一定要坚持使用信号传递通信内容,不要在线程内直接修改 UI 元素**!**

2. 线程关键规则

  • GUI 操作必须在主线程执行:子线程中禁止直接修改 UI 元素(如按钮文本、标签内容),否则会导致程序崩溃或界面异常。
  • 线程间通信只能通过信号与槽:子线程通过发射信号传递数据,主线程通过槽函数接收并更新 UI。
  • 避免全局变量共享 :跨线程共享数据需加锁(如 QMutex),防止数据竞争。

二、 几个关键知识点:

对象创建、线程启动、逻辑执行、终止退出、资源释放全流程拆解,结合 Qt 线程模型的核心规则说明以下几个知识点。

1. 线程管理器类:class QThread、线程管理器对象:QThread()和子线程对象的关系

QThread 是 Qt 框架中用于跨平台线程管理的核心类,封装了操作系统底层的线程实现(如 Windows 的 CreateThread、Linux 的 pthread),让开发者无需直接操作系统 API 即可实现多线程编程。因此,class QThread,或基于此创建的自己命名的MyQThread(QThread),是线程管理器的类定义。

QThread(),线程管理器对象也就是将定义的线程管理器类实例化以后的对象,比如thread=QThread() 或thread**=** MyQThread(),"thread"就是线程管理器类的实例对象,线程管理器对象是一个运行在主线程的子线程控制器,用于管理操作系统层面的 "子线程对象"。

而"子线程对象"(以下简称子线程 ) 是操作系统分配的独立执行流(CPU 调度的最小单位),run() 的逻辑就运行在这个实体中。运行线程管理器对象的start()函数,会创建一个子线程。重要:每个线程管理器对象 对应一个子线程,且只能对应一个子线程; 线程管理器对象创建以后,如果没有执行start()操作,子线程对象是不存在的。

理解了以上关系,就很容易写出共享和并行的线程管理代码:

python 复制代码
import sys

from PySide6 import QtWidgets
from PySide6.QtCore import Signal, QThread

app = QtWidgets.QApplication(sys.argv)

class MyWindow(QtWidgets.QWidget):
    task_finished = Signal()  # 任务完成信号
    def __init__(self):
        super().__init__()
        self.pushButton1 = QtWidgets.QPushButton("button1未激活", self)
        self.pushButton2 = QtWidgets.QPushButton("button2未激活", self)
        self.pushButton3 = QtWidgets.QPushButton("button3未激活", self)
        self.pushButton1.setObjectName("button1")
        self.pushButton2.setObjectName("button2")
        self.pushButton3.setObjectName("button3")

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.pushButton1)
        layout.addWidget(self.pushButton2)
        layout.addWidget(self.pushButton3)

        self.pushButton1.clicked.connect(self.on_pushButton_clicked)
        self.pushButton2.clicked.connect(self.on_pushButton_clicked)
        self.pushButton3.clicked.connect(self.on_pushButton_clicked)

    def on_pushButton_clicked(self):
        button = self.sender()  # 获取信号的发送者
        button.worker = worker_thread()  # 创建工作线程
        button_name = button.objectName()
        button.setText(f"{button_name}业务忙...")
        button.setEnabled(False)
        button.worker.start()  # 启动本线程

        def on_worker_finished():
            button.setText(f"{button_name}空闲中...")
            button.setEnabled(True)
            button.worker.deleteLater()   # 销毁线程实体
        button.worker.task_finished.connect(on_worker_finished)

# 共享的线程类定义
class worker_thread(QThread):
    task_finished = Signal()  # 任务完成信号
    def __init__(self):
        super().__init__()

    def run(self):   # 当线程启动时,执行此函数
        self.thread_job()  # 调用线程作业函数

    def thread_job(self):  # 线程作业函数
        self.msleep(5000)
        self.task_finished.emit()

widget = MyWindow()
widget.show()
sys.exit(app.exec())

2. 线程实体的几个关键函数:

**exec():**启动线程事件循环,一般存在于run()的内部,作用是使run()阻塞于此。

msleep()****/usleep(): QThread 提供的静态休眠方法 ,用于让当前执行的线程(通常是子线程)暂停一段时间(分别以毫秒和微秒为单位),是 Qt 跨平台的线程休眠方案(替代 Python 原生的 time.sleep(),更适配 Qt 线程模型)。

wait():等待线程完全退出(阻塞主线程)。


重点是下面这几个:

start()****:操作系统申请创建线程实体,并调度其执行;

run()****: 线程实体的入口函数(仅在新线程中执行);

stop()****: Qt 无原生 stop(),需自定义(优雅终止线程的逻辑);

  • run ()、start ()、stop () 的核心关系表
方法 调用线程 核心作用 与线程实体的关系
start() 主线程 向操作系统申请创建线程实体,并调度执行 触发线程实体创建 → 间接调用 run()
run() 子线程 线程实体的入口函数,执行核心业务逻辑 线程实体的 "执行体",run() 结束 = 线程实体终止
stop() 主线程 自定义方法,修改标志位让 run() 优雅退出 不直接操作线程实体,仅通知 run() 终止逻辑
  • 绝对禁止的操作

1.) 直接调用 run():不会创建线程实体,run() 会在主线程执行(等同于普通函数,阻塞 GUI);

2.)子线程中操作 GUI:run() 中不能修改按钮、标签等 GUI 控件,必须通过信号槽通知主线程;

3.)重复调用 start():一个 QThread 实例只能 start() 一次,重复调用会报错;

4.)用 terminate() 终止线程:强制杀死线程实体,可能导致资源泄漏、数据损坏。


3. isRunning()和isFinished()两个关键方法:

在 PySide6 中,QThread 提供了 isRunning()isFinished() 两个核心方法来判断线程的状态,这两个方法是线程状态管理的关键。

  • 核心概念
方法 作用 返回值 适用场景
isRunning() 判断线程是否处于运行中状态 boolTrue= 运行中,False= 未运行 / 已结束 检查线程是否还在执行任务
isFinished() 判断线程是否处于已完成状态 boolTrue= 已结束(正常 / 异常终止),False= 未开始 / 运行中 检查线程任务是否执行完毕
  • 线程状态流转

线程的核心状态流转:未启动(IsRunning=False, IsFinished=False)启动后运行中(IsRunning=True, IsFinished=False)运行结束(IsRunning=False, IsFinished=True)

  • 基础使用示例
python 复制代码
import sys
import time
from PySide6.QtWidgets import (QApplication, QWidget, QPushButton,
                               QVBoxLayout, QLabel)
from PySide6.QtCore import QThread, Signal


# 自定义工作线程(耗时任务)
class WorkerThread(QThread):
    # 定义信号,用于向主线程发送进度
    progress_signal = Signal(int)

    def run(self):
        """线程执行的核心方法(自动在子线程运行)"""
        for i in range(1, 11):
            if self.isInterruptionRequested():  # 检查是否被要求中断
                break
            time.sleep(0.5)  # 模拟耗时操作
            self.progress_signal.emit(i * 10)  # 发送进度
        # 运行结束后,isFinished() 会自动变为 True,isRunning() 变为 False


# 主窗口
class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("线程状态检查示例")
        self.resize(300, 200)

        # 初始化线程(注意:线程对象要定义为实例属性,避免被垃圾回收)
        self.worker = None

        # UI 组件
        self.status_label = QLabel("线程状态:未启动")
        self.start_btn = QPushButton("启动线程")
        self.check_btn = QPushButton("检查状态")
        self.stop_btn = QPushButton("停止线程")

        # 布局
        layout = QVBoxLayout()
        layout.addWidget(self.status_label)
        layout.addWidget(self.start_btn)
        layout.addWidget(self.check_btn)
        layout.addWidget(self.stop_btn)
        self.setLayout(layout)

        # 绑定信号槽
        self.start_btn.clicked.connect(self.start_thread)
        self.check_btn.clicked.connect(self.check_thread_status)
        self.stop_btn.clicked.connect(self.stop_thread)

    def start_thread(self):
        """启动线程"""
        # 避免重复启动(先检查状态)
        if self.worker and self.worker.isRunning():
            self.status_label.setText("线程状态:已在运行中,无需重复启动")
            return

        # 创建并启动线程
        self.worker = WorkerThread()
        self.worker.progress_signal.connect(self.update_progress)
        # 线程结束后自动回收资源(关键:避免内存泄漏)
        self.worker.finished.connect(self.on_thread_finished)
        self.worker.start()
        self.status_label.setText("线程状态:已启动")

    def check_thread_status(self):
        """检查线程状态(核心:调用 isRunning()/isFinished())"""
        if not self.worker:
            status = "未创建线程"
        else:
            running = self.worker.isRunning()
            finished = self.worker.isFinished()
            status = f"isRunning: {running}, isFinished: {finished}"
        self.status_label.setText(f"线程状态:{status}")

    def stop_thread(self):
        """停止线程"""
        if self.worker and self.worker.isRunning():
            self.worker.requestInterruption()  # 请求中断
            self.worker.quit()  # 退出事件循环
            self.worker.wait()  # 等待线程结束
            self.status_label.setText("线程状态:已手动停止")

    def update_progress(self, progress):
        """更新进度(主线程执行)"""
        self.status_label.setText(f"线程进度:{progress}% (isRunning: {self.worker.isRunning()})")

    def on_thread_finished(self):
        """线程结束回调"""
        self.status_label.setText(f"线程状态:正常结束 (isFinished: {self.worker.isFinished()})")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
  • 关键注意事项

1). 调用时机与线程安全

isRunning()/isFinished()线程安全的,可以在主线程直接调用(无需加锁)。

不要在 QThreadrun() 方法内频繁调用这两个方法(无实际意义,run() 运行时 isRunning() 必然为 True)。

2). 避免的误区

. isFinished()=True 不代表线程 "正常结束"------ 实际上,线程异常终止、手动停止也会导致 isFinished()=True

. 如果没有防重复启动的代码,启动前要检查 isRunning(),否则会触发崩溃。

. 线程结束后,建议调用 deleteLater() 释放资源。

3). 与finished 信号的配合(下面会讲到finished 信号)

isFinished() 是 "被动检查",而 finished 信号是 "主动通知",建议结合使用:

python 复制代码
# 使用finished主动监听线程结束
self.worker.finished.connect(lambda: print("线程结束,isFinished:", self.worker.isFinished()))
# 被动检查状态(按需使用)
if self.worker.isFinished():
    print("线程已结束")
  • 总结:

isRunning():判断线程是否在执行任务(核心用于 "是否可操作 / 重复启动检查")。

isFinished():判断线程是否已终止(核心用于 "资源回收 / 状态汇总")。

实际开发中,建议结合 finished 信号(主动通知)+ 状态检查(被动验证),实现线程的可靠管理。


4. start和finished两个关键信号:

在 PySide6 中,started 信号与 finished 信号是 "启动、结束" 的标志信号。

信号名 触发时机 对应操作 作用
started 线程的 run() 方法即将执行前 (线程已进入运行状态,isRunning() 变为 True 调用 QThread.start() 通知主线程:线程已启动,即将执行任务
finished 线程的 run() 方法执行完毕后 (线程进入终止状态,isFinished() 变为 True 线程正常结束 / 异常终止 / 手动停止 通知主线程:线程已终止
  • started 信号的关键特性

触发时机 :调用 QThread.start() 后,Qt 会先将线程加入事件循环,待线程真正进入运行状态(isRunning() 变为 True)、且 run() 方法执行前 ,发射 started 信号。时序:start() 调用 → 线程初始化 → 发射 started → 执行 run()

线程安全started 信号在主线程的事件循环中触发(而非子线程),因此可以安全地在槽函数中操作 UI(比如更新标签、按钮状态),无需额外的线程同步。

  • started 信号与 start() 方法的区别

start()方法:主动触发线程启动的操作;

started信号 :线程启动成功后的被动通知;示例:调用 worker.start() 后,即使线程 还没真正运行,方法会立即返回;而 started 信号会等到线程就绪后才发射,能准确判断 "线程是否真的启动了"。

python 复制代码
import sys

from PySide6 import QtWidgets
from PySide6.QtCore import Signal, QThread
from PySide6.QtWidgets import QLabel

app = QtWidgets.QApplication(sys.argv)

class MyWindow(QtWidgets.QWidget):
    task_finished = Signal()  # 任务完成信号(自定义的finished信号,信号发射意味着程序正常完成执行)
    def __init__(self):
        super().__init__()
        self.label = QLabel("等待线程中", self)
        self.pushButton1 = QtWidgets.QPushButton("button1未激活", self)
        self.pushButton2 = QtWidgets.QPushButton("button2未激活", self)
        self.pushButton3 = QtWidgets.QPushButton("button3未激活", self)
        self.pushButton1.setObjectName("button1")
        self.pushButton2.setObjectName("button2")
        self.pushButton3.setObjectName("button3")

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.pushButton1)
        layout.addWidget(self.pushButton2)
        layout.addWidget(self.pushButton3)

        self.pushButton1.clicked.connect(self.on_pushButton_clicked)
        self.pushButton2.clicked.connect(self.on_pushButton_clicked)
        self.pushButton3.clicked.connect(self.on_pushButton_clicked)

    def on_pushButton_clicked(self):
        button = self.sender()  # 获取信号的发送者
        button.worker = worker_thread()  # 创建工作线程
        button_name = button.objectName()
        button.worker.start()  # 启动本线程

        # def on_worker_finished():
        #     button.setText(f"{button_name}空闲中...")
        #     button.setEnabled(True)
        #     button.worker.deleteLater()   # 销毁线程实体
        # button.worker.task_finished.connect(on_worker_finished)  # 任务完成的信号,注意与线程自带的finished信号的区别

        def on_thread_started():
            button.setText(f"{button_name}业务忙...")
            button.setEnabled(False)
            self.label.setText(f"{button_name}线程已经启动")
        button.worker.started.connect(on_thread_started)  # 线程自带的started信号

        def on_thread_finished():
            self.label.setText(f"{button_name}线程执行完毕")
            button.setText(f"{button_name}空闲中...")
            button.setEnabled(True)
            button.worker.deleteLater()  # 销毁线程实体
        button.worker.finished.connect(on_thread_finished)   # 线程自带的finished信号

# 共享的线程类定义
class worker_thread(QThread):
    task_finished = Signal()  # 任务完成信号(自定义的完成信号,表示run()成功执行)
    def __init__(self):
        super().__init__()

    def run(self):   # 当线程启动时,执行此函数
        self.thread_job()  # 调用线程作业函数

    def thread_job(self):  # 线程作业函数
        self.msleep(5000)
        # self.task_finished.emit()

widget = MyWindow()
widget.show()
sys.exit(app.exec())

5. 生命周期

围绕创建→启动→运行→退出→销毁 核心链路展开,关键节点如下:

1). 初始化(创建)

通过 new QThread() 或栈构造创建QThread对象,此时仅初始化线程管理对象,未真正创建操作系统线程

可在此阶段设置线程属性(如优先级 setPriority()、线程名称 setObjectName()),绑定信号槽(如 started()finished())。

2). 启动(启动操作系统线程)

调用 start() 方法:Qt 底层创建操作系统线程,触发 started() 信号;

线程进入「就绪态」,等待系统调度执行,默认会调用 QThread 子类重写的 run() 方法(或默认 run() 中执行 exec() 启动事件循环)。

3). 运行(线程执行逻辑)

核心逻辑在 run() 中:

若重写 run() 且未调用 exec():执行完 run() 内代码后,线程直接退出

若调用 exec():启动线程本地事件循环,线程会持续运行,直到调用 quit()/exit() 终止事件循环;

运行中可通过 isRunning() 判断状态,通过 msleep()/usleep() 暂停,或通过信号槽与主线程通信。

4). 退出(终止运行)

主动退出:

无事件循环:run() 执行完毕,线程自动退出;

有事件循环:调用 quit()/exit(int code) 终止事件循环,run() 结束;

被动退出:调用 terminate()(强制终止,不推荐,可能导致资源泄漏),需谨慎使用;

退出时触发 finished() 信号,可通过 wait() 阻塞等待线程完全退出(避免主线程提前销毁线程对象)。

5). 销毁(释放资源)

线程退出后,QThread 对象本身不会自动销毁,需手动 delete(通过 deleteLater() 安全释放);

推荐在 finished() 信号中绑定 deleteLater(),避免线程未退出时销毁对象导致崩溃;

销毁后,操作系统线程资源被回收,QThread 对象变为「未运行」状态(isFinished() 为 true)。

6). 关键注意点

避免在主线程直接调用 delete QThread 对象,需等finished信号触发后释放;

run() 是线程的入口函数,仅在子线程执行,QThread 对象的其他方法(如 start()/quit())由创建它的线程(通常主线程)调用;

事件循环(exec())是线程能处理信号槽、定时器的前提,无事件循环的线程执行完 run() 即结束。

QThread 实例的销毁≠子线程销毁:需通过 quit()/wait() 正确终止子线程后,再释放 QThread 实例,否则会导致资源泄漏。

  • 从一个阶段性的例子来搞清楚线程的生命周期

阶段 1:QThread 对象创建(主线程)

python 复制代码
import sys

from PySide6.QtCore import QThread
from PySide6.QtWidgets import QApplication


class MyThread(QThread):
    def __init__(self):
        super().__init__()
        self.is_running = True  # 自定义终止标志位

    def run(self):  # 重写入口函数
        while self.is_running:
            print("子线程运行中...")
            self.msleep(1000)  # 子线程内休眠
        print("run()执行完毕,线程实体即将终止")

    def stop(self):  # 自定义停止方法
        self.is_running = False
        self.wait()  # 等待线程完全退出
app = QApplication(sys.argv)
# 1. 实例化QThread对象(仅在主线程创建控制器,无线程实体)
thread = MyThread()
print(f"QThread对象创建后,线程实体是否存在? {thread.isRunning()}")  # 输出:False
# # 2. 调用start():触发核心流程
# thread.start()
# print(f"调用start()后,线程实体是否运行? {thread.isRunning()}")  # 输出:True
# # 3. 主线程调用stop():修改标志位,让run()自行退出
# thread.stop()
# # 4.再次实例化QThread对象,并调用start()
# thread = MyThread()
# thread.start()
# print(f"再次调用start()后,线程实体是否运行? {thread.isRunning()}")  # 输出:True
# # 5. 再次调用stop()
# thread.stop()

sys.exit(app.exec())
  • 此时仅创建了 QThread 控制器对象,运行在主线程
  • 操作系统层面还未创建任何新线程,isRunning() 返回 False
  • 可初始化线程配置(如优先级、终止标志位),但无实际执行逻辑。

阶段 2:启动线程(start () 触发线程实体创建)

python 复制代码
# 2. 调用start():触发核心流程
thread.start()
print(f"调用start()后,线程实体是否运行? {thread.isRunning()}")  # 输出:True

start() 的核心行为(Qt 内部逻辑):

  1. 向操作系统内核申请创建线程实体(分配栈空间、CPU 调度资源);
  2. 操作系统将线程实体加入调度队列,等待 CPU 分配时间片;
  3. 线程实体被调度后,Qt 自动在该实体中调用 run() 方法;
  4. start() 本身是非阻塞 的(调用后立即返回主线程),不会等待 run() 执行。

阶段 3:run () 执行(线程实体的核心逻辑)

  • run() 是线程实体的唯一入口,仅在新线程中执行;
  • 若未重写 run(),Qt 原生 run() 会调用 exec() 启动线程的事件循环(支持子线程处理信号槽);
  • 若重写 run(),自定义逻辑会覆盖原生行为(如上面的循环逻辑);
  • run() 中的代码绝对不能直接操作 GUI 控件(如修改按钮文本),需通过信号槽通知主线程。

阶段 4:线程终止(stop () 优雅退出 vs 强制终止)

Qt 不推荐使用 terminate()(强制终止),因为会直接杀死线程实体,可能导致资源泄漏(如未关闭的文件、未释放的内存),因此通常自定义 stop() 方法:

python 复制代码
# 3. 主线程调用stop():修改标志位,让run()自行退出
thread.stop()
  • stop() 运行在主线程 ,仅修改共享标志位 is_running
  • 线程实体中的 run() 循环检测到 is_running=False,会退出循环,run() 执行完毕;
  • run() 执行完毕后,线程实体自动终止(进入 Finished 状态),isRunning() 变为 False

阶段 5:资源释放(线程实体 + QThread 对象)

  • 线程实体终止后,操作系统会回收其栈空间、CPU 调度资源,但 QThread 对象仍存在(主线程的控制器);
  • 必须调用 thread.deleteLater() 释放 QThread 对象的内存(Qt 父子对象机制推荐);
  • 一个 QThread 实例只能调用一次 start (),若需重新执行线程逻辑,需重新创建实例。

阶段 6:再一次调用start()

python 复制代码
# 4.再次实例化QThread对象,并调用start()和stop()
thread = MyThread()
thread.start()
print(f"再次调用start()后,线程实体是否运行? {thread.isRunning()}")  # 输出:True

阶段 7:再一次调用stop()

python 复制代码
# 5. 再次调用stop()
thread.stop()

可通过 QThread 的状态方法判断线程阶段:

方法 作用 典型场景
isRunning() 判断线程实体是否正在运行 start() 后为 True,run() 结束后为 False
isFinished() 判断线程实体是否已终止 run() 结束后为 True
wait(msecs=-1) 阻塞调用者线程,等待线程实体终止 主线程等待子线程完成(慎用,避免阻塞 GUI)
  • 总结

生命周期核心链路QThread对象创建start() 申请线程实体 → 操作系统调度 → run() 执行 → stop() 触发退出 → run() 结束 → 线程实体终止 → deleteLater() 释放控制器。

核心原则start() 是线程实体的 "准生证",run() 是线程实体的 "执行逻辑",stop() 是 "优雅退场指令";

线程实体的生命周期 = run() 的执行周期,run() 结束则线程实体消亡;

线程管理器对象≠线程实体,线程实体在start()以后才有,run()结束后消亡;线程管理器对象销毁需手动调用 deleteLater()

最佳实践 :永远用 "标志位 + 循环退出" 实现 stop(),避免 terminate()

子线程仅处理业务逻辑,GUI 操作必须通过信号槽回主线程;

线程使用完毕后务必释放 QThread 对象,避免内存泄漏。

三、线程使用的两种核心方式

方式 1:继承 QThread(重写 run 方法)

这是最直观的方式,适合简单的耗时任务,重写 QThread.run() 方法,该方法内的代码会在子线程执行。

步骤:

  1. 定义进程管理器类继承 QThread
  2. 重写 run() 方法(耗时操作写在此处);
  3. 定义信号(用于子线程向主线程传递数据);
  4. 主线程创建子线程实例,连接信号与槽;
  5. 启动 / 停止线程。

前面的所有范例代码都是使用的这种方法。

方式 2:moveToThread

继承 QThread 的方式存在局限性(如线程内创建的对象默认属于该线程,难以复用),moveToThread 是更灵活的方式:将耗时操作封装到 QObject 子类中,再将该对象移到 QThread 中执行。

优势:

  • 一个线程可运行多个任务对象,复用线程;
  • 任务对象的生命周期与线程解耦,便于管理;
  • 更符合 Qt 的信号与槽设计理念。

步骤:

  1. 定义 QObject 任务子类,封装耗时操作(作为槽函数);
  2. 定义QObject 任务子类信号(传递进度 / 结果);
  3. 主线程创建 QThread 和任务对象;
  4. 将任务对象移到线程中(moveToThread());
  5. 连接信号触发任务执行,连接任务信号到主线程槽函数;
  6. 启动线程。
python 复制代码
import sys
import time

from PySide6 import QtWidgets
from PySide6.QtCore import Signal, QThread, QObject, QEventLoop, Slot
from PySide6.QtWidgets import QLabel, QRadioButton, QButtonGroup

app = QtWidgets.QApplication(sys.argv)

class MyWindow(QtWidgets.QWidget):

    def __init__(self):
        super().__init__()
        self.label = QLabel("空闲中。。。", self)
        self.btn_group = QButtonGroup()
        self.radio1 = QRadioButton("worker1", self)
        self.radio2 = QRadioButton("worker2", self)
        self.radio3 = QRadioButton("worker3", self)
        self.btn_group.addButton(self.radio1)
        self.btn_group.addButton(self.radio2)
        self.btn_group.addButton(self.radio3)
        self.radio1.setChecked(True)  # 默认选中worker1业务
        self.startButton = QtWidgets.QPushButton("启动业务", self)
        self.stopButton = QtWidgets.QPushButton("停止业务", self)
        self.stopButton.setEnabled(False)
        self.worker_id = 1  # 默认选中worker1业务

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.label)
        layout.addWidget(self.radio1)
        layout.addWidget(self.radio2)
        layout.addWidget(self.radio3)
        layout.addWidget(self.startButton)
        layout.addWidget(self.stopButton)

        self.worker = worker()  # 创建业务对象
        self.thread = QThread()  # 创建线程对象
        self.worker.moveToThread(self.thread)  # 将业务对象移动到线程中

        self.startButton.clicked.connect(self.on_startButton_clicked)
        self.stopButton.clicked.connect(self.on_stopButton_clicked)
        self.radio1.toggled.connect(self.on_radio_toggled)
        self.radio2.toggled.connect(self.on_radio_toggled)
        self.radio3.toggled.connect(self.on_radio_toggled)

        self.worker.task_signal.connect(self.on_task_signal)  # 连接任务信号到处理函数
        self.worker.task_finished.connect(self.on_task_finished)   # 连接任务完成信号到处理函数

        # self.thread.started.connect(self.worker.start_event_loop)

    def on_startButton_clicked(self):
        for btn in self.btn_group.buttons():
            btn.setEnabled(False)
        self.startButton.setEnabled(False)
        self.stopButton.setEnabled(True)
        if not self.thread.isRunning():
            self.thread.start()  # 启动线程
        self.worker.task_start.emit(self.worker_id)  # 触发Worker执行id对应的任务

    def on_stopButton_clicked(self):
        self.startButton.setEnabled(True)
        self.stopButton.setEnabled(False)
        self.worker.is_running = False

    def on_radio_toggled(self):
        w = self.btn_group.checkedButton().text()
        if w == "worker1":
            self.worker_id = 1
        elif w == "worker2":
            self.worker_id = 2
        else:
            self.worker_id = 3
        self.startButton.setEnabled(True)

    def on_task_finished(self):
        self.label.setText("任务完成。。。")
        for btn in self.btn_group.buttons():
            btn.setEnabled(True)
        self.startButton.setEnabled(True)
        self.stopButton.setEnabled(False)

    def on_task_signal(self, id, value):
        self.label.setText(f"来自worker{id}:{value}")

# 共享的线程类定义
class worker(QObject):
    work_start = Signal() # 工作启动信号,本例中的工作是持续运行的(exec())
    task_start = Signal(int)  # 任务启动信号,当worker收到task_start信号时,执行一次对应的任务
    task_finished = Signal()  # 任务完成信号(自定义的完成信号,表示run()成功执行)
    task_signal = Signal(int, int)  # 任务返回到主进程的数据信号
    def __init__(self):
        super().__init__()
        self.is_running = True
        self.work_id = None
        self.task_start.connect(self.on_task_start)
        self.loop = QEventLoop()  # 线程事件循环(保持线程常驻)
        self.work_start.connect(self.loop.exec)  # 连接工作启动信号到事件循环,阻塞子进程并接受和发送信号

    def work1_job1(self):  # 线程作业函数
        for i in range(1, 101):
            if not self.is_running:
                break
            time.sleep(0.05)
            self.task_signal.emit(self.work_id, i)

    def work1_job2(self):  # 线程作业函数
        for i in range(100, 201):
            if not self.is_running:
                break
            time.sleep(0.05)
            self.task_signal.emit(self.work_id, i)

    def work1_job3(self):  # 线程作业函数
        for i in range(200, 301):
            if not self.is_running:
                break
            time.sleep(0.05)
            self.task_signal.emit(self.work_id, i)

    @Slot(int)
    def on_task_start(self, id):
        self.is_running = True
        if id == 1:
            self.work_id = 1
            self.work1_job1()
        elif id == 2:
            self.work_id = 2
            self.work1_job2()
        else:
            self.work_id = 3
            self.work1_job3()
        self.task_finished.emit()

    @Slot()
    def on_task_stop(self):
        self.is_running = False
        print("on_task_stop")

    @Slot()
    def start_event_loop(self):
        """线程启动后,进入事件循环(常驻等待信号)"""
        self.loop.exec()  # 阻塞在此,直到调用loop.quit()


    def stop_loop(self):
        """退出事件循环(程序关闭时调用)"""
        self.loop.quit()

# 程序关闭时清理线程
def close_app():
    if hasattr(widget, 'worker') and hasattr(widget, 'thread'):
        widget.worker.stop_loop()
        widget.thread.quit()
        widget.thread.wait()

if __name__ == "__main__":
    widget = MyWindow()
    widget.show()
    # 注册程序退出回调,确保线程正常清理
    app.aboutToQuit.connect(close_app)
    sys.exit(app.exec())

moveToThread()的本质是创建一个线程,然后把另一个QObject对象移动到创建好的线程里面运行。

四、线程管理关键要点

1. 线程的启动与停止
  • 启动 :调用 QThread.start(),线程进入运行状态,执行 run() 或任务对象的槽函数;
  • 停止
    • 禁止直接调用 QThread.terminate()(强制终止,可能导致资源泄漏、数据损坏);
    • 推荐方式:通过标志位(如 is_running)让线程自行退出循环,再调用 quit() + wait() 等待线程结束。
2. 线程间通信
  • 信号与槽:是唯一安全的跨线程通信方式,Qt 会自动处理线程间的事件循环,确保信号安全传递;

  • 信号参数 :支持基本类型(int、str、list 等)、自定义类型(需注册:qRegisterMetaType);

  • 示例:自定义类型的信号

    复制代码
    from PySide6.QtCore import qRegisterMetaType
    
    # 定义自定义类型
    class ResultData:
        def __init__(self, code, msg):
            self.code = code
            self.msg = msg
    
    # 注册自定义类型(必须)
    qRegisterMetaType(ResultData, "ResultData")
    
    # 定义信号
    result_signal = Signal(ResultData)
3. 线程同步与锁

当多个线程访问共享数据时,需使用锁防止数据竞争:

  • QMutex:互斥锁,确保同一时间只有一个线程访问共享资源;

  • QMutexLocker:简化锁的使用(自动加锁 / 解锁);

    from PySide6.QtCore import QMutex, QMutexLocker

    class Worker(QObject):
    def init(self):
    super().init()
    self.mutex = QMutex()
    self.shared_data = 0

    复制代码
      @Slot()
      def update_data(self):
          # 自动加锁,退出作用域时解锁
          locker = QMutexLocker(self.mutex)
          self.shared_data += 1
          print(f"共享数据:{self.shared_data}")
4. 线程池管理(QRunnable和QThreadPool、globalInstance)

在 PySide6 中,QRunnableQThreadPool(尤其是全局实例 QThreadPool.globalInstance())是实现多线程任务调度的核心组件,适用于处理耗时操作(如 IO、计算),避免阻塞主线程(UI 线程)。以下是详细的使用指南、原理和示例:

  • 核心概念
组件 作用
QRunnable 可被 QThreadPool 执行的任务基类 ,需重写 run() 方法定义任务逻辑
QThreadPool 线程池管理器,负责创建、复用线程,调度 QRunnable 任务执行
globalInstance() 获取程序全局唯一的线程池实例(无需手动创建 / 销毁,推荐优先使用)
  • 核心特性

线程复用:线程池会复用已创建的线程,避免频繁创建 / 销毁线程的性能开销;

任务队列:当线程池满时,新任务会进入队列等待;

自动管理:全局线程池由 Qt 自动管理生命周期,无需手动释放;

非阻塞:任务在子线程执行,不阻塞主线程(UI 响应)。

  • 基础使用步骤

步骤 1: 定义自定义 QRunnable 子类

需重写 run() 方法(任务的核心逻辑),可通过信号(需结合 QObject)向主线程传递结果。

**步骤 2:**提交任务到全局线程池

通过 QThreadPool.globalInstance().start(runnable) 提交任务。

  • 完整示例

示例 1:基础用法(无返回值)

复制代码
import sys
import time
from PySide6.QtCore import QRunnable, QThreadPool, Qt
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QTextEdit
)

# 自定义任务类(继承QRunnable)
class MyTask(QRunnable):
    def __init__(self, task_id: int):
        super().__init__()
        self.task_id = task_id

    # 重写run方法:任务的核心逻辑(在子线程执行)
    def run(self):
        print(f"任务{self.task_id}开始执行,线程ID: {hex(id(self))}")
        # 模拟耗时操作(如IO/计算)
        time.sleep(2)
        print(f"任务{self.task_id}执行完成")

# 主窗口类
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QRunnable + 全局线程池示例")
        self.resize(400, 300)

        # UI组件
        self.text_edit = QTextEdit()
        self.btn_start = QPushButton("启动10个任务")
        self.btn_start.clicked.connect(self.start_tasks)

        # 布局
        central_widget = QWidget()
        layout = QVBoxLayout(central_widget)
        layout.addWidget(self.text_edit)
        layout.addWidget(self.btn_start)
        self.setCentralWidget(central_widget)

        # 获取全局线程池实例
        self.thread_pool = QThreadPool.globalInstance()
        # 设置线程池最大线程数(可选,默认是CPU核心数*2)
        self.thread_pool.setMaxThreadCount(5)
        self.text_edit.append(f"线程池最大线程数: {self.thread_pool.maxThreadCount()}")

    def start_tasks(self):
        # 提交10个任务到全局线程池
        for i in range(10):
            task = MyTask(i + 1)
            # 提交任务(非阻塞,立即返回)
            self.thread_pool.start(task)
            self.text_edit.append(f"已提交任务{i + 1}")

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

示例 2:带结果回调(结合 QObject 信号)

QRunnable 本身不继承 QObject,无法直接定义信号,需通过内部 QObject 实现:

python 复制代码
import sys
import time
from PySide6.QtCore import QRunnable, QThreadPool, QObject, Signal, Slot
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QTextEdit
)

# 信号载体(继承QObject)
class TaskSignals(QObject):
    # 定义信号:完成(任务ID,结果)、出错(任务ID,错误信息)
    finished = Signal(int, str)
    error = Signal(int, str)

# 带回调的任务类
class TaskWithCallback(QRunnable):
    def __init__(self, task_id: int):
        super().__init__()
        self.task_id = task_id
        self.signals = TaskSignals()  # 信号实例

    def run(self):
        try:
            print(f"任务{self.task_id}执行中...")
            time.sleep(2)
            # 模拟计算结果
            result = f"任务{self.task_id}结果:{self.task_id * 10}"
            # 发送完成信号
            self.signals.finished.emit(self.task_id, result)
        except Exception as e:
            # 发送错误信号
            self.signals.error.emit(self.task_id, str(e))

# 主窗口
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QRunnable 带回调")
        self.resize(400, 300)

        self.text_edit = QTextEdit()
        self.btn_start = QPushButton("启动任务")
        self.btn_start.clicked.connect(self.start_task)

        central_widget = QWidget()
        layout = QVBoxLayout(central_widget)
        layout.addWidget(self.text_edit)
        layout.addWidget(self.btn_start)
        self.setCentralWidget(central_widget)

        self.thread_pool = QThreadPool.globalInstance()

    @Slot(int, str)  # 槽函数(接收任务完成信号)
    def on_task_finished(self, task_id: int, result: str):
        self.text_edit.append(f"任务{task_id}完成:{result}")

    @Slot(int, str)  # 槽函数(接收任务错误信号)
    def on_task_error(self, task_id: int, error: str):
        self.text_edit.append(f"任务{task_id}出错:{error}")

    def start_task(self):
        # 创建任务
        task = TaskWithCallback(1)
        # 连接信号与槽(主线程执行槽函数)
        task.signals.finished.connect(self.on_task_finished)
        task.signals.error.connect(self.on_task_error)
        # 提交任务
        self.thread_pool.start(task)
        self.text_edit.append("任务已提交")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
  • 关键 API 说明

QThreadPool 核心方法 / 属性

方法 / 属性 作用
globalInstance() 静态方法,返回全局线程池实例(单例)
start(runnable, priority=0) 提交 QRunnable 任务,priority 越高越优先执行(默认 0)
setMaxThreadCount(n) 设置线程池最大线程数(默认 CPU 核心数 * 2)
maxThreadCount() 获取最大线程数
activeThreadCount() 获取当前活跃线程数
waitForDone(msecs=-1) 等待所有任务完成(msecs=-1 表示无限等待,主线程慎用
clear() 清空等待队列(已开始的任务不受影响)

QRunnable 核心方法

方法 作用
run() 必须重写,定义任务逻辑(子线程执行)
setAutoDelete(b) 设置任务完成后是否自动删除(默认 True,无需手动释放)
autoDelete() 获取自动删除状态
  • 注意事项

线程安全 :子线程(run() 内)禁止直接操作 UI 组件(如 QTextEdit),必须通过信号 - 槽机制通知主线程操作;

自动删除 :默认 setAutoDelete(True),任务完成后自动销毁,若需复用任务需设为 False

全局线程池QThreadPool.globalInstance() 是全局唯一的,所有地方共享,无需手动创建新的 QThreadPool(除非有特殊隔离需求);

耗时任务 :适合 CPU 密集型 / IO 密集型任务,避免在 run() 中执行无限循环(会占用线程池线程);

异常处理run() 内的异常不会自动抛出到主线程,需手动捕获并通过信号传递。

  • 适用场景

批量处理耗时任务(如文件下载、数据解析);

后台计算(不阻塞 UI);

高并发短任务(线程池复用线程提升性能)。

通过 QRunnable + QThreadPool.globalInstance(),可以优雅地实现 PySide6 的多线程任务调度,兼顾性能和易用性。

5. 线程资源清理
  • 窗口关闭时,需手动停止线程并释放资源,避免内存泄漏;

  • 使用 QThread.finished 信号连接 deleteLater(),自动释放线程对象;

  • 重写 closeEvent 方法,确保线程正常退出:

    复制代码
    def closeEvent(self, event):
        # 停止任务
        if hasattr(self, 'worker'):
            self.worker.stop_task()
        # 退出线程并等待
        if hasattr(self, 'thread') and self.thread.isRunning():
            self.thread.quit()
            self.thread.wait()
        event.accept()

五、常见问题与解决方案

1. 界面卡顿
  • 原因:耗时操作未放到子线程,阻塞主线程;
  • 解决:将所有耗时逻辑移到子线程,仅在主线程更新 UI。
2. 跨线程操作 UI 崩溃
  • 原因:子线程直接修改 UI 元素;
  • 解决:通过信号发射数据,主线程槽函数更新 UI。
3. 线程重复启动
  • 原因:未检查线程状态(isRunning());
  • 解决:启动前判断 if not self.thread.isRunning()
4. 线程无法停止
  • 原因:未设置退出标志位,或耗时操作无中断点;
  • 解决:在耗时循环中定期检查标志位,确保线程能退出。

总结

方式 适用场景 优点 缺点
继承 QThread 简单、单一的耗时任务 代码直观,易理解 线程复用性差
moveToThread 复杂、可复用的任务 灵活,解耦,易管理 代码稍多
QThreadPool 大量短期小任务 高效,低资源开销 不适合长期运行的任务

核心原则:

  1. 主线程只处理 UI,子线程处理耗时操作;
  2. 线程间通信仅通过信号与槽;
  3. 安全停止线程,避免强制终止;
  4. 共享数据加锁,防止数据竞争。
相关推荐
todoitbo3 小时前
告别复杂笔记软件!Memos+cpolar,让你的笔记随时随地可用
网络·笔记·内网穿透·cpolar·软件·memos
superman超哥3 小时前
仓颉Option类型的空安全处理深度解析
c语言·开发语言·c++·python·仓颉
2401_841495643 小时前
【LeetCode刷题】跳跃游戏Ⅱ
数据结构·python·算法·leetcode·数组·贪心策略·跳跃游戏
Data_agent3 小时前
OOPBUY模式淘宝1688代购系统搭建指南
开发语言·爬虫·python
张哈大3 小时前
AI Ping 上新限免:GLM-4.7 与 MiniMax-M2.1 实测对比
人工智能·python
乘凉~3 小时前
【Linux作业】Limux下的python多线程爬虫程序设计
linux·爬虫·python
钓鱼的肝3 小时前
GESP系列(3级)小杨的储蓄
开发语言·数据结构·c++·笔记·算法·gesp
xwill*3 小时前
pytorch中项目配置文件的管理与导入方式
人工智能·python
世转神风-4 小时前
QEventLoop与QTimer联动
qt
weixin_462446234 小时前
【实践原创】 dify创建获取天气的Agent
学习·dify