Qt多线程之线程子类

Qt 子类QThread 线程处理


箴言:

《江城子·十年生死两茫茫》 — 苏轼

十年生死两茫茫,
不思量,自难忘。
千里孤坟,无处话凄凉。

纵使相逢应不识,
尘满面,鬓如霜。
夜来幽梦忽还乡,
小轩窗,正梳妆。

相顾无言,惟有泪千行。
料得年年肠断处,
明月夜,短松冈。

介绍

在 Qt 多线程开发中,QThread 子类化是最基础、也是最容易被误解的一种用法。在使用时,往往只停留在“能跑”的层面,而对其线程归属、调用链、执行上下文缺乏清晰认知,最终导致各种隐蔽问题。

要理解:继承 QThread 时,线程到底是怎么工作的?哪些代码在子线程?哪些不在?为什么 run() 不能直接调用?

1. QThread 子类的最小模型

1.1 单次运行Demo

class UMyThread : public QThread
{
    Q_OBJECT
public:
    explicit UMyThread(QObject* parent = nullptr) : QThread(parent) {}

Q_SIGNALS:
    void sig_trans_msg(int val);

public:
    static QString threadIdHex()
    {
        quintptr tid = reinterpret_cast<quintptr>(QThread::currentThreadId());
        return QString("0x%1")
                .arg(tid, sizeof(void*) * 2, 16, QChar('0'));
    }

    void doWork()
    {
        qDebug() << "do Work" << threadIdHex();

        sleep(2);
    }

protected:
    virtual void run() override
    {
        qDebug() << "Start Run: ... " << threadIdHex();

        doWork();

        qDebug() << "End Run ..." << threadIdHex();
    }
};

调用:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    UMyThread t;
    t.start();
	// 线程开辟需要时间, 不加上, 会先执行 下面的语句.
    QThread::msleep(100);

    qDebug() << "Main End... " << UMyThread::threadIdHex();

    return a.exec();
}

Start Run: ...  "0x0000000000004900"
do Work "0x0000000000004900"
Main End...  "0x0000000000004468"
End Run ... "0x0000000000004900"

很多人以为这已经足够理解 QThread,但实际上这里隐藏了多个关键问题:

  • start() 到底做了什么?
  • run() 是在哪个线程执行的?
  • UMyThread 对象本身在哪个线程?
  • run 之外的函数在哪执行?

这些问题如果不搞清楚,后续所有多线程代码都会有隐患。

1.2 从startrun

主线程 
  ↓ 
调用 QThread::start()  
  ↓ 
Qt 创建一个新的操作系统线程(pthread / Win32 thread)
  ↓ 
新线程启动(线程入口函数)  
  ↓ 
调用 QThread::run()(注意:此时已经在新线程中)

start() 做了两件事:

  • 创建线程(真正的 OS 线程)
  • 安排线程入口(最终调用 run)

run 只是一个“线程执行函数”,类似:std::thread();

run() 决定“做什么”,start() 决定“在哪里做”

run() 里面调用的函数都属于线程调用。run() 创建的对象属于在子线程创建。其他在构造函数中,或者其他位置创建的均属创建该对象的的线程。

2. 跨线程信息交互

2.1 子线程 --> 主线程

2.1.1 方式一:信号(推荐,最标准)

    void doWork()
    {
        qDebug() << "do Work" << threadIdHex();

        sleep(2);

        Q_EMIT sig_trans_msg(10);
    }

    UMyThread t;
    QObject::connect(&t, &UMyThread::sig_trans_msg, &a, [](int val)
    {
        quintptr tid = reinterpret_cast<quintptr>(QThread::currentThreadId());
        qDebug() << "Slot: " << val <<  QString("0x%1").arg(tid, sizeof(void*) * 2, 16, QChar('0'));
    });

    t.start();

    QThread::msleep(100);

Start Run: ...  "0x0000000000000be0"
do Work "0x0000000000000be0"
主线程...  "0x0000000000002124"
End Run ... "0x0000000000000be0"
Slot:  10 "0x0000000000002124"

2.1.2 方式二:共享变量 + 同步

注意,run 执行完成后,才会发送数据,或者等待结果。return t.resut( ); 同步阻塞。

    UMyThread t;

    qDebug() << qint64(t.currentThreadId());

    QObject::connect(&t, &UMyThread::sig_trans_msg, &a, [&](int v)
    {

        qDebug() << "线程传输: " << v << "线程Id: " << qint64(t.currentThreadId());;
    }, Qt::QueuedConnection);


    t.start();
    t.wait();
18676
Start Thread...  4256
Doing...  4256
End Thread...  4256
线程传输:  41 线程Id:  18676

2.1.3 方式三:QMetaObject::invokeMethod(高级控制)

直接在 Run 中调用 invokedMethod 方法执行逻辑。

void UMyThread::run()
{
    qDebug() << "Start Thread... " << qint64(currentThreadId());

    doWork();

    QMetaObject::invokeMethod(m_pTarget, "sltRecvSubThreadVal", Qt::QueuedConnection, Q_ARG(int, rand() % 10));

    sleep(2);

    qDebug() << "End Thread... " << qint64(currentThreadId());
}

class UTestA : public QObject
{
    Q_OBJECT
public:

public Q_SLOTS:
    void sltRecvSubThreadVal(int v)
    {
        qDebug() << "UTestA : " << v;
    }
};

2.1.4 方式四:自定义事件传输

在子线程的Run函数中,通过 自定义事件传递给其他具有事件循环的线程(例如主线程)。

void USubThread::run()
{
	QCoreApplication::postEvent(mainReceiver, new MyEvent(100) );
}

2.2 主线程 --> 子线程

主线程 → 子线程,使用 QueuedConnection,如果子线程 run() 没有 exec(),信号槽不会自动执行

因为在 QThread的子类中,发送和接收所在的线程都属于主线程,所以无论有没有 exec(), 槽函数的执行是在主线程的。

UTargetObj obj;
QObject::connect(&obj, &UTargetObj::sig_send_2_subThread, &t, &UMyThread::doWork, Qt::QueuedConnection);
Q_EMIT obj.sig_send_2_subThread(222);

2.2.1 线程安全队列 / 消息队列

主线程向子线程投递数据结构,子线程不断消费:

  • 使用 QQueue + QMutexQQueue + QWaitCondition
  • 或者 lock-free 队列(高性能场景,如日志系统)
class WorkerThread : public QThread
{
    QMutex mutex;
    QWaitCondition cond;
    QQueue<int> queue;
protected:
    void run() override
    {
        while (!isInterruptionRequested()) {
            mutex.lock();
            if (queue.isEmpty())
                cond.wait(&mutex); // 等待数据
            int value = queue.dequeue();
            mutex.unlock();
            process(value);
        }
    }
public:
    void pushValue(int value)
    {
        QMutexLocker locker(&mutex);
        queue.enqueue(value);
        cond.wakeOne();
    }
};

3. 使用案例

3.1 单次长时间任务

class LongTaskThread : public QThread
{
    Q_OBJECT
public:
    LongTaskThread(QObject* parent = nullptr) : QThread(parent) {}

signals:
    void progress(int value);   // 子线程 → 主线程发送进度
    void finished();            // 子线程完成任务

protected:
    void run() override
    {
        qDebug() << "任务开始,线程id:" << quint64(QThread::currentThreadId());

        const int total = 3;
        for (int i = 0; i <= total; ++i)
        {
            QThread::msleep(100);  // 模拟耗时计算
            emit progress(i);   // 发射信号给主线程
        }

        qDebug() << "任务完成,线程id:" << quint64(QThread::currentThreadId());
        emit finished();
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    LongTaskThread task;

    QObject::connect(&task, &LongTaskThread::progress,  [](int value)     
    {
        qDebug() << "主线程收到进度:" << value
                 << "thread id:" << quint64(QThread::currentThreadId());
    });

    QObject::connect(&task, &LongTaskThread::finished, [&]()           
    {
        qDebug() << "主线程收到任务完成信号";
        task.quit();
        task.wait();
        QCoreApplication::quit();
    });

    task.start();  // 启动子线程,执行 run()

    return a.exec();
}

任务开始,线程id: 9612
主线程收到进度: 0 thread id: 9612
主线程收到进度: 1 thread id: 9612
主线程收到进度: 2 thread id: 9612
主线程收到进度: 3 thread id: 9612
任务完成,线程id: 9612
主线程收到任务完成信号
QThread::wait: Thread tried to wait on itself

3.2 高频短时间任务

外界传入数据给QThread线程

#pragma once

#include <QQueue>
#include <QMutex>
#include <QWaitCondition>

template<typename T>
class ThreadSafeQueue
{
public:
    ThreadSafeQueue() = default;
    ~ThreadSafeQueue() = default;

    // ---------- push ----------
    void push(const T& value)
    {
        {
            QMutexLocker locker(&m_mutex);
            m_queue.enqueue(value);
        }
        m_notEmpty.wakeOne();
    }

    void push(T&& value)
    {
        {
            QMutexLocker locker(&m_mutex);
            m_queue.enqueue(std::move(value));
        }
        m_notEmpty.wakeOne();
    }

    // ---------- 阻塞 pop ----------
    bool waitPop(T& value)
    {
        QMutexLocker locker(&m_mutex);

        while (m_queue.isEmpty() && !m_stop)
        {
            m_notEmpty.wait(&m_mutex);
        }

        if (m_queue.isEmpty())
            return false; // stop 触发

        value = std::move(m_queue.dequeue());
        return true;
    }

    // ---------- 非阻塞 pop ----------
    bool tryPop(T& value)
    {
        QMutexLocker locker(&m_mutex);

        if (m_queue.isEmpty())
            return false;

        value = std::move(m_queue.dequeue());
        return true;
    }

    // ---------- 批量 pop(高频优化) ----------
    int tryPopAll(QVector<T>& out)
    {
        QMutexLocker locker(&m_mutex);

        int count = 0;
        while (!m_queue.isEmpty())
        {
            out.push_back(std::move(m_queue.dequeue()));
            ++count;
        }
        return count;
    }

    // ---------- 停止 ----------
    void stop()
    {
        {
            QMutexLocker locker(&m_mutex);
            m_stop = true;
        }
        m_notEmpty.wakeAll();
    }

    // ---------- 状态 ----------
    bool empty() const
    {
        QMutexLocker locker(&m_mutex);
        return m_queue.isEmpty();
    }

    int size() const
    {
        QMutexLocker locker(&m_mutex);
        return m_queue.size();
    }

private:
    mutable QMutex m_mutex;
    QWaitCondition m_notEmpty;
    QQueue<T> m_queue;
    bool m_stop{false};
};

class WorkerThread : public QThread
{
public:
    static ThreadSafeQueue<QString> queue;

    void pushTaks(const QString& msg)
    {
        queue.push( msg);
    }

Q_SIGNALS:
    void sigProcessed(int v);

protected:
    void run() override
    {
        QString msg;

        while (queue.waitPop(msg))
        {
            qDebug() << "处理:" << msg
                     << "thread:" << QThread::currentThreadId();

            // ----- 向主线程发送信号 -----
              emit sigProcessed(qrand() % 360);
        }

        exec();

        qDebug() << "线程退出";
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    WorkerThread t;
    t.start();

    WorkerThread t2;
    t2.start();

    // 生产数据
    for (int i = 0; i < 1000; ++i)
    {
        t.pushTaks(QString("msg %1").arg(i));
    }

    // 停止
    QTimer::singleShot(2000, [&](){
        t.queue.stop();
        t.wait();
        t2.queue.stop();
        t2.wait();
    });

    qDebug() << QThread::currentThread();


    return a.exec();
}

处理: "msg 0" thread: 0x14e4
处理: "msg 1" thread: 0x17bc
QThread(0x1d167410)
处理: "msg 2" thread: 0x14e4
处理: "msg 3" thread: 0x17bc
处理: "msg 4" thread: 0x14e4
处理: "msg 5" thread: 0x17bc
posted on 2026-04-04 22:35  Hakuon  阅读(23)  评论(0)    收藏  举报