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

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

问题原因

  1. 主线程析构阶段的事件循环状态:主窗口进入析构函数时,Qt的主事件循环已经处于停止或清理流程中,此时发送的Qt::QueuedConnection类型信号无法被正常投递到子线程的事件队列,导致子线程的槽函数根本没机会执行,open_flag一直为true,run循环无法终止,程序卡住。
  2. 继承QThread的写法误区:QThread对象本身是在主线程中创建的,其成员函数默认运行在主线程上下文;如果你的Stop_Cur_Thread槽没有明确绑定到子线程事件循环,即使使用QueuedConnection,也可能因为主线程事件循环停止而无法触发。

解决方案

方案1:改用DirectConnection+原子变量(快速修复)

因为Stop_Cur_Thread槽只是修改一个布尔变量,只要把变量改成原子类型,直接连接就是线程安全的,不会有竞态问题:

  1. 修改串口检测线程类中的open_flag为原子类型:
#include <atomic>
// ...
std::atomic<bool> open_flag{true};
  1. 连接信号槽时明确指定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的方式,能更清晰地分离线程对象和业务逻辑,避免线程上下文混淆:

  1. 创建串口检测的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;
};
  1. 在主窗口中初始化线程和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();
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 00:20:39