Qt子线程无法正常退出问题排查与求助
Qt多线程安全退出问题:串口检测线程无法通过QueuedConnection终止
问题背景
我开发了一个包含主GUI线程和两个子线程的Qt程序:
- 一个子线程负责定时检测系统可用串口
- 另一个子线程负责串口数据收发
核心需求是:关闭主窗口时,安全退出这两个子线程,避免资源泄漏或崩溃。
当前卡在继承QThread实现的串口检测线程上:
- 我在主窗口的析构函数中发送
Stop_Serial_Monitor_Thread信号,连接到串口检测线程的Stop_Cur_Thread槽函数(该槽仅将线程内部的open_flag设为false,以此终止run函数里的循环) - 当使用
Qt::QueuedConnection连接信号槽时,程序无法正常退出;尝试在run函数中添加exec()或QCoreApplication::processEvents()均无效,只有移除该连接类型(改用默认直接连接)才能正常退出 - 如果把信号发送逻辑移到主窗口的其他函数(比如按钮点击事件),槽函数能正常执行,但不符合“关闭窗口时自动退出线程”的需求
系统环境:Windows 11、Qt 5.9.0 MinGW 32bit
问题原因
- 主线程析构阶段的事件循环状态:主窗口进入析构函数时,Qt的主事件循环已经处于停止或清理流程中,此时发送的
Qt::QueuedConnection类型信号无法被正常投递到子线程的事件队列,导致子线程的槽函数根本没机会执行,open_flag一直为true,run循环无法终止,程序卡住。 - 继承QThread的写法误区:QThread对象本身是在主线程中创建的,其成员函数默认运行在主线程上下文;如果你的
Stop_Cur_Thread槽没有明确绑定到子线程事件循环,即使使用QueuedConnection,也可能因为主线程事件循环停止而无法触发。
解决方案
方案1:改用DirectConnection+原子变量(快速修复)
因为Stop_Cur_Thread槽只是修改一个布尔变量,只要把变量改成原子类型,直接连接就是线程安全的,不会有竞态问题:
- 修改串口检测线程类中的
open_flag为原子类型:
#include <atomic> // ... std::atomic<bool> open_flag{true};
- 连接信号槽时明确指定
Qt::DirectConnection:
connect(this, &MainWindow::Stop_Serial_Monitor_Thread, serialMonitorThread, &SerialMonitorThread::Stop_Cur_Thread, Qt::DirectConnection);
这样主窗口析构时发送的信号会直接调用子线程的槽函数,立即修改open_flag,子线程的run循环会在下一次判断时退出。
方案2:在closeEvent中处理线程退出(更稳妥)
不要在主窗口析构函数中发送停止信号,而是重写closeEvent事件处理函数,此时主窗口还未进入析构,主事件循环仍正常工作,QueuedConnection的信号能被正常投递:
void MainWindow::closeEvent(QCloseEvent *event) { // 发送停止信号给串口检测线程 emit Stop_Serial_Monitor_Thread(); // 等待线程退出,设置3秒超时避免无限等待 serialMonitorThread->quit(); if (!serialMonitorThread->wait(3000)) { serialMonitorThread->terminate(); serialMonitorThread->wait(); } // 同理处理串口通信线程 emit Stop_Serial_Comm_Thread(); serialCommThread->quit(); if (!serialCommThread->wait(3000)) { serialCommThread->terminate(); serialCommThread->wait(); } event->accept(); QMainWindow::closeEvent(event); }
这种方式下,即使使用Qt::QueuedConnection,信号也能正常到达子线程的槽函数,配合quit()和wait()可以安全终止线程。
方案3:改用QObject+moveToThread写法(官方推荐)
Qt官方不推荐直接继承QThread的写法,更推荐用QObject子类+moveToThread的方式,能更清晰地分离线程对象和业务逻辑,避免线程上下文混淆:
- 创建串口检测的Worker类:
#include <QObject> #include <atomic> #include <QThread> class SerialMonitorWorker : public QObject { Q_OBJECT public: SerialMonitorWorker() : open_flag(true) {} public slots: void startMonitoring() { while (open_flag) { // 这里写检测系统串口的逻辑 // 比如枚举串口、对比变化等 QThread::msleep(1000); // 模拟检测间隔 } } void stopMonitoring() { open_flag = false; } private: std::atomic<bool> open_flag; };
- 在主窗口中初始化线程和Worker:
#include "SerialMonitorWorker.h" #include <QThread> // 主窗口类成员变量 QThread* m_serialMonitorThread; SerialMonitorWorker* m_serialMonitorWorker; // 主窗口构造函数中初始化 m_serialMonitorThread = new QThread(this); m_serialMonitorWorker = new SerialMonitorWorker(); m_serialMonitorWorker->moveToThread(m_serialMonitorThread); // 连接信号槽 connect(m_serialMonitorThread, &QThread::started, m_serialMonitorWorker, &SerialMonitorWorker::startMonitoring); connect(this, &MainWindow::Stop_Serial_Monitor_Thread, m_serialMonitorWorker, &SerialMonitorWorker::stopMonitoring); connect(m_serialMonitorThread, &QThread::finished, m_serialMonitorWorker, &SerialMonitorWorker::deleteLater); connect(m_serialMonitorThread, &QThread::finished, m_serialMonitorThread, &QThread::deleteLater); // 启动线程 m_serialMonitorThread->start();
- 在closeEvent中停止线程(同方案2的closeEvent逻辑):
void MainWindow::closeEvent(QCloseEvent *event) { emit Stop_Serial_Monitor_Thread(); m_serialMonitorThread->quit(); if (!m_serialMonitorThread->wait(3000)) { m_serialMonitorThread->terminate(); m_serialMonitorThread->wait(); } // 处理串口通信线程... event->accept(); QMainWindow::closeEvent(event); }
这种写法下,Worker的所有槽函数默认运行在子线程的事件循环中,Qt::QueuedConnection能正常工作,线程退出逻辑更可靠。
内容的提问来源于stack exchange,提问作者happy Mr mighty
相关产品推荐
相关产品推荐

