2. QThread基础:创建第一个QThread子类、重写run()方法、启动与停止线程
好,咱们正式开始动手写多线程代码了。
这一章,我带你走一遍最基础的流程:继承QThread、重写run()、启动、停止。这四个步骤,说白了就是多线程编程的“起手式”。
我个人习惯,在项目里只要涉及后台任务,第一反应就是先搭一个QThread子类的骨架。为什么?因为这样结构最清晰,后续维护也方便。
核心要点:QThread本身并不是一个线程,它是一个线程管理对象。真正的线程在调用start()时诞生,在run()返回时消亡。
2.1 创建QThread子类
先看代码。我写一个最简单的例子:一个工作线程,专门用来打印数字。
// workerthread.h
#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H
#include <QThread>
#include <QDebug>
class WorkerThread : public QThread
{
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = nullptr);
~WorkerThread();
void stopWork(); // 外部调用的停止接口
protected:
void run() override; // 重写run()
private:
volatile bool m_stopped; // 停止标志
};
#endif // WORKERTHREAD_H
这里有个细节:m_stopped 我用了 volatile 关键字。为什么?因为 m_stopped 会被两个线程同时访问——主线程设置它,工作线程读取它。不加 volatile,编译器可能会优化掉这个变量的读取,导致线程停不下来。
小提示:在C++11及以后,更推荐用 std::atomic<bool> 替代 volatile。不过老项目里 volatile 依然常见,你看着办。
2.2 重写run()方法
run() 是线程的入口函数。线程启动后,会执行 run() 里的代码。run() 返回,线程就结束了。
// workerthread.cpp
#include "workerthread.h"
WorkerThread::WorkerThread(QObject *parent)
: QThread(parent)
, m_stopped(false)
{
}
WorkerThread::~WorkerThread()
{
// 确保线程退出时资源被清理
stopWork();
wait(); // 等待线程真正结束
}
void WorkerThread::run()
{
qDebug() << "线程开始工作,线程ID:" << QThread::currentThreadId();
int count = 0;
while (!m_stopped) {
qDebug() << "打印数字:" << count++;
QThread::msleep(500); // 休眠500ms,模拟耗时操作
// 防止无限循环,加个上限
if (count > 20) {
break;
}
}
qDebug() << "线程结束工作";
}
嗯,这里要注意:run() 里不能直接操作UI。我在项目中遇到过,新手直接在 run() 里调用 ui->label->setText(),结果程序直接崩溃。为什么?因为Qt的UI控件不是线程安全的,必须在主线程操作。
警告:永远不要在 run() 中直接操作UI控件。如果需要更新界面,请使用信号槽机制(后面章节会讲)。
2.3 启动与停止线程
启动很简单,调用 start() 就行。停止则需要一点技巧。
void WorkerThread::stopWork()
{
m_stopped = true;
}
调用 stopWork() 后,run() 里的 while 循环会在下一次判断条件时退出。这是一种协作式停止,不是强制终止。
为什么不用 terminate()?我曾经在早期项目里用过一次,后果很惨——线程被强制杀死时,可能正持有某个锁,或者正在写文件。结果就是死锁、数据损坏。从那以后,我再也不敢用 terminate() 了。
最佳实践:永远使用协作式停止(设置标志位),不要使用 terminate()。除非你明确知道自己在做什么,并且能承受后果。
2.4 完整的使用示例
看看怎么在主程序里用这个线程:
// main.cpp
#include <QCoreApplication>
#include "workerthread.h"
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerThread worker;
worker.start(); // 启动线程
// 3秒后停止线程
QTimer::singleShot(3000, &worker, &WorkerThread::stopWork);
// 等待线程结束
worker.wait();
qDebug() << "主线程退出";
return a.exec();
}
运行结果大致如下:
线程开始工作,线程ID: 0x1234
打印数字: 0
打印数字: 1
打印数字: 2
...
打印数字: 5
线程结束工作
主线程退出
你想想看,这个流程其实很清晰:创建 → 启动 → 工作 → 停止 → 等待结束。多线程编程的骨架,就是这么回事。
2.5 知识体系图
下面这张图,帮你理清本章的核心逻辑:
2.6 避坑指南
- 析构顺序问题:我曾经在析构函数里直接
delete worker,但线程还在跑。结果程序崩溃。正确做法:先stopWork(),再wait(),最后才删除对象。 - 重复启动:一个QThread对象只能启动一次。如果想再次运行,需要重新创建对象。我见过有人连续调用两次
start(),结果Qt直接报错。 - run() 里不要有死循环:如果 run() 里是 while(true) 且没有停止条件,线程永远退不出来。记得加一个退出条件,或者定期检查停止标志。
个人习惯:我通常在 run() 的循环里加一个 QThread::yieldCurrentThread(),让出CPU时间片。这样系统调度更平滑,不会把CPU占满。
好了,这一章的内容就这些。你只要记住四个步骤:继承、重写、启动、停止。多线程编程的大门,你已经迈进去了。