You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5 QThread未并行执行:工作线程与GUI线程ID一致问题排查

问题描述

尝试为侧边QPushButton加载缩略图,计划为每张图片创建一个QThread实现并行加载,但日志显示所有Worker的线程ID完全相同,未实现预期的并行执行(CPU为4核8线程)。

原始代码

class Worker(QObject):
    finished = pyqtSignal(QIcon)

    def run(self, path: str):
        import threading
        logging.debug(f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
        icon = QIcon
        icon.addFile(path)
        logging.debug(f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later')
        self.finished.emit(icon)

# 创建线程与Worker的逻辑
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
# 连接信号槽
self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.set_icon)  # 实际为 lambda icon: a_btn.setIcon(icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
# 启动线程
logging.debug(f'Thread started at {(t1 := time.time())} seconds')
self.thread.start(priority=0)

日志输出

2023-05-14T13:27:04 Thread started at 1684063624.1749578 seconds
2023-05-14T13:27:04 Thread started at 1684063624.3233523 seconds
2023-05-14T13:27:04 Thread started at 1684063624.3353844 seconds
2023-05-14T13:27:04 Thread started at 1684063624.5469468 seconds
2023-05-14T13:27:04 Thread started at 1684063624.7655282 seconds
2023-05-14T13:27:06 Widget setup completed at 1684063626.6284823 seconds
2023-05-14T13:27:06 Worker started at 1684063626.6485348 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.6585634 seconds, 0.01 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.6585634 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.8139749 seconds, 0.155 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.814978 seconds on thread 10100
2023-05-14T13:27:06 Worker finished @ 1684063626.8250043 seconds, 0.01 seconds later
2023-05-14T13:27:06 Worker started at 1684063626.826007 seconds on thread 10100
2023-05-14T13:27:07 Worker finished @ 1684063627.0385983 seconds, 0.213 seconds later
2023-05-14T13:27:07 Worker started at 1684063627.0395803 seconds on thread 10100
2023-05-14T13:27:07 Worker finished @ 1684063627.2581596 seconds, 0.219 seconds later

环境:PyCharm 2022专业版、Python 3.11、Windows 10;待加载文件大小300KB至50MB,存储在7200RPM HDD中。


原因分析

  1. 直接调用Worker.run方法未触发线程切换
    代码中self.thread.started.connect(lambda: self.worker.run(self.thumbnail_path))是通过lambda直接调用worker.run(),这种方式属于同步调用,会在主线程中执行run方法,而非切换到子线程。Qt的跨线程执行依赖信号槽的自动调度:只有当信号连接到Worker的槽函数时,槽函数才会在Worker所在的子线程执行,直接调用方法不会触发线程上下文切换。

  2. QIcon初始化错误
    代码中icon = QIcon是将QIcon类赋值给变量,而非创建QIcon实例,后续调用icon.addFile(path)实际是调用类方法,会导致功能异常,但这并非线程ID相同的直接原因。


修复方案

方案1:通过信号触发Worker执行

修改Worker类,新增启动信号,通过信号传递图片路径,确保run方法在子线程执行:

class Worker(QObject):
    finished = pyqtSignal(QIcon)
    start_task = pyqtSignal(str)  # 新增启动任务的信号,传递图片路径

    def __init__(self):
        super().__init__()
        self.start_task.connect(self.run)  # 将启动信号连接到run方法

    def run(self, path: str):
        import threading
        logging.debug(f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
        icon = QIcon()  # 修复:创建QIcon实例
        icon.addFile(path)
        logging.debug(f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later')
        self.finished.emit(icon)

# 创建线程与Worker的逻辑修改
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)

# 信号槽连接调整
self.thread.started.connect(lambda: self.worker.start_task.emit(self.thumbnail_path))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.set_icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)

# 启动线程
logging.debug(f'Thread started at {(t1 := time.time())} seconds')
self.thread.start(priority=0)

方案2:将图片路径作为Worker属性

如果不需要动态传递路径,也可以在Worker初始化时传入路径,避免lambda的上下文问题:

class Worker(QObject):
    finished = pyqtSignal(QIcon)

    def __init__(self, path: str):
        super().__init__()
        self.path = path

    def run(self):
        import threading
        logging.debug(f'Worker started at {(t1 := time.time())} seconds on thread {threading.get_ident()}')
        icon = QIcon()
        icon.addFile(self.path)
        logging.debug(f'Worker finished @ {time.time()} seconds, {(time.time() - t1):.3} seconds later')
        self.finished.emit(icon)

# 创建线程与Worker的逻辑修改
self.thread = QThread()
self.worker = Worker(self.thumbnail_path)
self.worker.moveToThread(self.thread)

# 信号槽连接调整
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.set_icon)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)

# 启动线程
logging.debug(f'Thread started at {(t1 := time.time())} seconds')
self.thread.start(priority=0)

内容的提问来源于stack exchange,提问作者An average guy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 03:10:01