在 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() |
判断线程是否处于运行中状态 | bool:True= 运行中,False= 未运行 / 已结束 |
检查线程是否还在执行任务 |
isFinished() |
判断线程是否处于已完成状态 | bool:True= 已结束(正常 / 异常终止),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() 是线程安全的,可以在主线程直接调用(无需加锁)。
不要在 QThread 的 run() 方法内频繁调用这两个方法(无实际意义,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 内部逻辑):
- 向操作系统内核申请创建线程实体(分配栈空间、CPU 调度资源);
- 操作系统将线程实体加入调度队列,等待 CPU 分配时间片;
- 线程实体被调度后,Qt 自动在该实体中调用
run()方法; 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() 方法,该方法内的代码会在子线程执行。
步骤:
- 定义进程管理器类继承
QThread; - 重写
run()方法(耗时操作写在此处); - 定义信号(用于子线程向主线程传递数据);
- 主线程创建子线程实例,连接信号与槽;
- 启动 / 停止线程。
前面的所有范例代码都是使用的这种方法。
方式 2:moveToThread
继承 QThread 的方式存在局限性(如线程内创建的对象默认属于该线程,难以复用),moveToThread 是更灵活的方式:将耗时操作封装到 QObject 子类中,再将该对象移到 QThread 中执行。
优势:
- 一个线程可运行多个任务对象,复用线程;
- 任务对象的生命周期与线程解耦,便于管理;
- 更符合 Qt 的信号与槽设计理念。
步骤:
- 定义
QObject任务子类,封装耗时操作(作为槽函数); - 定义
QObject任务子类信号(传递进度 / 结果); - 主线程创建
QThread和任务对象; - 将任务对象移到线程中(
moveToThread()); - 连接信号触发任务执行,连接任务信号到主线程槽函数;
- 启动线程。
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 中,QRunnable 和 QThreadPool(尤其是全局实例 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 | 大量短期小任务 | 高效,低资源开销 | 不适合长期运行的任务 |
核心原则:
- 主线程只处理 UI,子线程处理耗时操作;
- 线程间通信仅通过信号与槽;
- 安全停止线程,避免强制终止;
- 共享数据加锁,防止数据竞争。