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 从start到run
主线程
↓
调用 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 + QMutex或QQueue + 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
本文来自博客园,作者:Hakuon,转载请注明原文链接:https://www.cnblogs.com/Hakuon/p/19821944
浙公网安备 33010602011771号