前言
在PySide6/PyQT 多线程编程中,线程池也是重要的一项知识点,线程池是一种高效管理和调度多个线程执行任务的方式。
通过结合线程池(QThreadPool)和任务类(QRunnable),可以轻松地实现并发执行任务、提高应用程序的性能,并更好地控制代码逻辑和资源的管理。
值得注意的是,在使用 QRunnable 和 QThreadPool 的线程池中,每个任务(QRunnable 对象)会被分配到不同的线程执行,每个线程拥有自己的任务和局部数据。因此,默认情况下,每个任务在自己的线程中是相互独立的,不会直接共享资源。
知识点📖📖
本文用到的几个PySide6/PyQT的知识点及链接。
作用 | 链接 |
---|---|
对象间通信的机制,允许对象发送和接收信号 | Signal |
用于响应Signal信号的方法 | Slot |
可运行的任务类,用于封装要在后台线程中执行的代码逻辑 | QRunnable |
线程池类,用于管理和调度多个线程执行 QRunnable 任务 | QThreadPool |
基础概念
QRunnable
一个接口类,用于表示可以在线程中执行的任务。通过继承 QRunnable 并实现 run 方法,可以定义任务的逻辑。
QThreadPool
一个管理线程的线程池类。它可以管理和执行 QRunnable 对象,并自动管理线程的创建、回收和重用。
QThreadPool 可以限制并发线程的数量,防止资源过度占用。
QRunnable 更适合用于执行短期、轻量级的任务,而 QThread 更适合用于需要更多控制和状态管理的长期任务。
使用 QRunnable 和 QThreadPool 的优势在于线程的创建和管理交由线程池处理,可以避免频繁创建和销毁线程,提高效率,并根据系统资源和任务数量自动调整线程的数量。
创建线程池步骤
创建自定义任务类(QRunnable)
- 创建继承自 QRunnable 的自定义任务类。在任务类中实现 run() 方法,该方法包含要在后台线程中执行的代码逻辑。
class Worker(QRunnable):
def __init__(self, func, *args, **kwargs):
super().__init__()
self.func = func
self.args = args
self.kwargs = kwargs
def run(self):
# 执行任务函数,并传递参数
self.func(*self.args, **self.kwargs)
创建线程池对象(QThreadPool)
- 在应用程序中创建一个线程池对象。通过直接实例化 QThreadPool 或使用 QThreadPool.globalInstance() 方法获取全局线程池实例。
def main():
# 创建线程池对象
threadpool = QThreadPool()
创建任务对象并提交给线程池
- 创建一个任务对象,将其实例化为自定义任务类的实例。
- (可选)可以通过构造函数传递任务所需的函数、参数或其他数据。
- 使用线程池的 start() 方法将任务对象提交给线程池执行。
# 示例任务函数
def task_func():
# 耗时操作
# 创建任务对象并提交给线程池
task1 = Worker(task_func, "John")
threadpool.start(task1)
task2 = Worker(task_func, "Alice")
threadpool.start(task2)
与QThread相比较
优势有挺多的,参考如下:
- 将任务的执行和线程管理分离,使代码更具可维护性和可扩展;
- 管理和控制多个任务的并发执行,以最大程度地利用系统资源;
- 在需要执行大量独立任务或耗时操作的情况下,提供一种高效的并发处理方式;
- 提高应用程序的响应性,通过并行执行任务,减少主线程的负载,避免阻塞用户界面;
- 通过将任务逻辑封装在 QRunnable 中,并使用 QThreadPool 进行任务的调度和管理,轻松实现多线程应用程序,提高性能和响应性,并且更好地控制和组织代码。
- 自动管理线程的创建、回收和重用,避免频繁创建和销毁线程,从而减少了线程切换和资源消耗。线程池可以重用线程,避免了重复创建线程的开销。
总结
综上所述,使用 QRunnable 和 QThreadPool 相对于直接使用 QThread 提供了更高级的线程管理功能、更好的性能优化、更好的并发控制和更好的可扩展性。
它通过自动管理线程的创建和回收、动态调整线程数量、限制并发线程的数量等机制,减少了线程切换和资源消耗,提高了整体系统性能和稳定性。
同时,将任务逻辑封装在 QRunnable 中,使代码更清晰和可维护,并提供了更灵活的任务管理方式。
常用方法
threadpool = QThreadPool()
常用的方法列举在这里,
代码 | 释义 |
---|---|
start(QRunnable) | 将一个任务(QRunnable 对象)提交给线程池执行 |
activeThreadCount() | 返回当前正在执行任务的线程数 |
maxThreadCount() | 返回线程池允许的最大线程数 |
waitForDone(msecs = -1) | 等待线程池中的所有任务执行完成,或者等待指定的毫秒数。如果设置为 -1,则会一直等待直到所有任务完成 |
clear() | 清除线程池中所有待执行的任务,但不会停止正在执行的任务 |
代码展示
这只是一份简单的线程池模板,没啥用~
import time
from PySide6.QtCore import (QRunnable, QThreadPool, Qt)
# 自定义的工作任务类
class Worker(QRunnable):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f"Worker {self.name} started")
time.sleep(2) # 模拟一些耗时的工作
print(f"Worker {self.name} finished")
def main():
# 创建线程池
thread_pool = QThreadPool()
# 设置最大线程数
thread_pool.setMaxThreadCount(4)
# 创建工作任务并将其添加到线程池
for i in range(1, 6):
worker = Worker(f"Task {i}")
thread_pool.start(worker)
# 在等待所有任务完成之前,阻塞主线程
thread_pool.waitForDone()
print("All tasks completed")
if __name__ == "__main__":
main()
代码释义
-
定义工作任务类:定义了一个名为
Worker
的自定义类,它继承自QRunnable
类。这个类表示一个工作任务,具体的工作逻辑定义在run
方法中。 -
Worker
类的__init__
方法:这个方法用于初始化工作任务对象。在这个示例中,将每个工作任务命名为name
,并将其保存在self.name
属性中。 -
Worker
类的run
方法:这个方法表示工作任务的执行逻辑。在示例中,打印工作任务的名称,然后使用time.sleep
方法模拟一些耗时的工作(2秒),最后再次打印工作任务的名称。 -
main
函数:程序的入口点。在这个函数中执行以下操作:- 创建线程池:使用
QThreadPool()
创建一个全局的线程池对象thread_pool
。 - 设置最大线程数:使用
setMaxThreadCount
方法设置线程池的最大线程数为4。 - 创建工作任务并添加到线程池:使用一个循环创建了5个工作任务,每个任务都有一个唯一的名称。然后,使用
start
方法将这些工作任务添加到线程池中,以便并行执行。 - 阻塞主线程直到任务完成:使用
waitForDone
方法阻塞主线程,直到所有任务完成执行。 - 打印所有任务完成的消息:在所有任务完成后,打印"All tasks completed"的消息。
- 创建线程池:使用
这份代码创建了一个线程池并将多个工作任务添加到线程池中执行。
每个任务都是通过继承QRunnable
类并实现run
方法来定义的。通过使用线程池,这些工作任务可以在多个线程中并行执行,从而提高程序的效率。
最后,等待所有任务完成后,打印出任务完成的消息。
总结🎈🎈
本文介绍了PySide6/PyQT线程池的基本概念和使用方法。介绍了QRunnable和QThreadPool的作用,以及它们结合在一起的优势。
通过使用QRunnable和QThreadPool,可以将任务的执行和线程管理分离,提高应用程序的性能和响应性。
我建议常见的多线程场景都用上线程池,省事~
后话
本次分享到此结束,
see you~~🐱🏍🐱🏍