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

PySide6中QRunnable随机SegFault或线程未启动问题排查

PySide6多线程随机启动失败/段错误问题分析与修复

问题描述

参照PySide6多线程教程改编代码后,线程有时无法正常启动,还会随机触发Segmentation Fault。可复现问题的最小代码如下:

import sys
import time

from PySide6.QtCore import QThreadPool, Slot, Signal, QObject, QRunnable
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QListWidget

class Device:
    def long_function(self):
        time.sleep(1)
        return ['ciao','test']
    
class Ui_MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(800, 600)
        self.setWindowTitle('Test1')
        
        layout = QVBoxLayout()
        self.button_start = QPushButton('Start')
        layout.addWidget(self.button_start)
        self.log = QListWidget()
        layout.addWidget(self.log)
        centralwidget = QWidget()
        centralwidget.setLayout(layout)
        self.setCentralWidget(centralwidget)

class WorkerSignals(QObject):
    result = Signal(object)
class Worker(QRunnable):
    def __init__(self,fn):
        super().__init__()
        self.fn = fn
        self.signals = WorkerSignals()
    
    @Slot()
    def run(self):
        res = self.fn()
        self.signals.result.emit(res)

class Controller:
    def __init__(self,ui,dev):
        self.ui = ui
        self.dev = dev

        self.ui.button_start.clicked.connect(self.start)

        self.threadpool = QThreadPool()
        self.ui.show()

    def start(self):
        worker = Worker(lambda : self.dev.long_function())
        worker.signals.result.connect(self.update)
        self.threadpool.start(worker)
        self.ui.log.addItem('Started')
    
    @Slot()
    def update(self,l):
        self.ui.log.addItem('Ende   d')
        self.ui.log.addItems(l)

def main():
    app = QApplication(sys.argv)
    mainwindow = Ui_MainWindow()
    dev = Device()
    controller = Controller(mainwindow,dev)
    
    sys.exit(app.exec())
    
if __name__ == '__main__':
    main()

问题分析

核心原因是Worker实例的生命周期管理不当:

  • 在Controller.start()方法中,Worker是局部变量,方法执行完毕后,Python垃圾回收机制可能提前回收该对象。
  • 虽然QThreadPool会处理QRunnable的C++层对象,但Python层的Worker实例被回收后,其内部的WorkerSignals(QObject子类)也会被销毁。若此时后台线程仍在运行,或信号result尚未完成发送/处理,就会导致访问已销毁的对象,进而引发段错误或线程异常终止。

修复方案

确保Worker实例在任务完成前不会被Python垃圾回收,以下两种方法均可解决问题:

方法1:在Controller中维护Worker引用

在Controller中添加列表存储活跃的Worker,任务完成后移除引用:

class Controller:
    def __init__(self,ui,dev):
        self.ui = ui
        self.dev = dev
        self.ui.button_start.clicked.connect(self.start)
        self.threadpool = QThreadPool()
        self.active_workers = []  # 存储活跃Worker实例
        self.ui.show()

    def start(self):
        worker = Worker(lambda : self.dev.long_function())
        worker.signals.result.connect(self.update)
        # 任务完成后从列表移除引用
        worker.signals.result.connect(lambda: self.active_workers.remove(worker))
        self.active_workers.append(worker)
        self.threadpool.start(worker)
        self.ui.log.addItem('Started')
    
    @Slot()
    def update(self,l):
        self.ui.log.addItem('Ended')  # 修正原代码的空格拼写错误
        self.ui.log.addItems(l)

方法2:禁用自动删除,手动管理Worker生命周期

修改Worker类,禁用自动删除,任务完成后手动触发销毁:

class Worker(QRunnable):
    def __init__(self,fn):
        super().__init__()
        self.fn = fn
        self.signals = WorkerSignals()
        self.setAutoDelete(False)  # 禁用自动删除
    
    @Slot()
    def run(self):
        res = self.fn()
        self.signals.result.emit(res)
        self.signals.result.disconnect()  # 断开所有信号连接
        self.deleteLater()  # 手动触发对象销毁

内容的提问来源于stack exchange,提问作者Filippo Volpe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 03:43:10