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 知识体系图

下面这张图,帮你理清本章的核心逻辑:

QThread基础:创建与生命周期 1. 继承QThread class Worker : QThread 2. 重写run() void run() override 3. 启动线程 worker.start() 4. 停止线程 stopWork() 线程生命周期 创建对象 → start() → run() 执行 → run() 返回 → 线程结束 |← 主线程管理 →|←———————— 工作线程运行 ————————→| ✅ 正确做法 • 使用标志位协作停止 • 析构时调用 wait() • 通过信号槽更新UI ❌ 错误做法 • 使用 terminate() 强制终止 • 在 run() 中直接操作UI • 忘记调用 wait() 导致崩溃

2.6 避坑指南

  • 析构顺序问题:我曾经在析构函数里直接 delete worker,但线程还在跑。结果程序崩溃。正确做法:先 stopWork(),再 wait(),最后才删除对象。
  • 重复启动:一个QThread对象只能启动一次。如果想再次运行,需要重新创建对象。我见过有人连续调用两次 start(),结果Qt直接报错。
  • run() 里不要有死循环:如果 run() 里是 while(true) 且没有停止条件,线程永远退不出来。记得加一个退出条件,或者定期检查停止标志。

个人习惯:我通常在 run() 的循环里加一个 QThread::yieldCurrentThread(),让出CPU时间片。这样系统调度更平滑,不会把CPU占满。

好了,这一章的内容就这些。你只要记住四个步骤:继承、重写、启动、停止。多线程编程的大门,你已经迈进去了。

可可精品资料库