3. 启动与停止线程:start()、quit()、wait()、terminate()的正确用法
线程的启动和停止,看起来简单,其实坑不少。
我见过不少新手,直接调用terminate()来杀线程,结果程序莫名其妙崩溃。也见过有人忘了调用wait(),程序退出时直接段错误。
说白了,这四个函数各有各的脾气。用对了,线程管理得心应手;用错了,调试到怀疑人生。
3.1 start():让线程跑起来
start()是线程的起点。调用它之后,Qt会创建一个新的执行线程,然后执行run()函数里的代码。
核心要点:
- 一个线程对象只能调用一次
start()。重复调用会触发警告,且不会生效。 - 调用
start()后,线程不一定立即执行。它只是进入了就绪队列,等待操作系统调度。 - 如果你继承
QThread,记得重写run()方法。否则线程会直接退出。
// 正确的启动方式
class WorkerThread : public QThread
{
Q_OBJECT
protected:
void run() override
{
// 在这里执行耗时操作
for (int i = 0; i < 100; ++i) {
qDebug() << "Working..." << i;
QThread::msleep(100);
}
}
};
// 使用
WorkerThread *thread = new WorkerThread(this);
thread->start(); // 启动线程
我的习惯:启动线程后,我会立即检查isRunning(),确认线程真的跑起来了。虽然很少失败,但多一步检查总没坏处。
3.2 quit():优雅地通知线程退出
quit()是Qt推荐的线程退出方式。它不会强制终止线程,而是告诉线程的事件循环:「你可以结束了」。
注意,quit()只对运行了事件循环的线程有效。如果你的线程没有调用exec(),那quit()就没什么用。
// 带事件循环的线程
class EventLoopThread : public QThread
{
Q_OBJECT
public:
void stop()
{
quit(); // 通知事件循环退出
wait(); // 等待线程真正结束
}
protected:
void run() override
{
exec(); // 启动事件循环
}
};
我曾经踩过的坑:在run()里写了一个while(true)循环,然后调用quit()。结果线程根本停不下来。为什么?因为quit()只退出事件循环,不会中断你的自定义循环。你需要自己检查一个标志位来退出。
3.3 wait():等待线程真正结束
wait()的作用是阻塞当前线程,直到目标线程执行完毕。它接受一个超时参数(毫秒),默认是ULONG_MAX,表示一直等下去。
我个人建议,每次停止线程后都要调用wait()。否则,线程可能还在后台运行,而你的程序已经销毁了线程对象,导致崩溃。
| 场景 | 建议做法 |
|---|---|
| 程序退出前 | 先quit(),再wait() |
| 切换工作线程 | 先quit(),再wait(),再start() |
| 强制终止 | 先terminate(),再wait()(不推荐) |
// 安全的线程停止流程
void stopThread(QThread *thread)
{
if (thread->isRunning()) {
thread->quit(); // 第一步:通知退出
thread->wait(3000); // 第二步:等待最多3秒
if (thread->isRunning()) {
// 超时了还没结束,说明有问题
qWarning() << "Thread did not stop in time!";
thread->terminate(); // 最后手段
thread->wait(); // 别忘了再等一次
}
}
}
避坑指南:不要在GUI线程里调用wait()且不设超时。我曾经这么干过,结果界面卡死了。因为wait()会阻塞主线程的事件循环,导致界面无法响应。如果你必须等,设个超时,比如1000毫秒。
3.4 terminate():最后的杀手锏
terminate()会强制终止线程,不管线程正在做什么。它不释放资源,不调用析构函数,不通知任何对象。
说白了,这就是个核武器。能不用就别用。
使用terminate()的风险:
- 线程可能正在持有互斥锁,强制终止会导致死锁。
- 线程可能正在分配内存,强制终止会导致内存泄漏。
- 线程可能正在写入文件,强制终止会导致数据损坏。
// 只有在万不得已时才使用
if (thread->isRunning()) {
thread->terminate(); // 强制终止
thread->wait(); // 必须等待,否则线程对象可能还在运行
}
我的建议:永远不要在生产代码里直接调用terminate()。如果你发现线程无法正常退出,那说明你的线程设计有问题。应该去修复线程的退出逻辑,而不是用暴力手段。
3.5 完整的线程生命周期管理
下面这张图展示了线程从创建到销毁的完整流程:
从图中可以看出,quit()和terminate()是两条不同的路径。前者是绿色通道,安全可靠;后者是红色警报,非不得已不要走。
3.6 实战中的最佳实践
说了这么多理论,来点实际的。我在项目中总结了一套线程管理的模板,分享给你:
class SafeThread : public QThread
{
Q_OBJECT
public:
explicit SafeThread(QObject *parent = nullptr)
: QThread(parent), m_stopRequested(false) {}
void requestStop()
{
m_stopRequested = true; // 设置标志位
quit(); // 通知事件循环退出
}
bool isStopRequested() const
{
return m_stopRequested;
}
protected:
void run() override
{
// 自定义循环中检查标志位
while (!m_stopRequested) {
// 执行你的工作
QThread::msleep(100);
}
}
private:
QAtomicInt m_stopRequested; // 原子操作,线程安全
};
// 使用示例
SafeThread *worker = new SafeThread(this);
worker->start();
// 停止时
worker->requestStop();
if (!worker->wait(3000)) {
qWarning() << "Worker thread did not stop, forcing...";
worker->terminate();
worker->wait();
}
总结一下:
start():启动线程,只能调用一次。quit():优雅退出,配合事件循环使用。wait():等待线程结束,必须调用。terminate():强制终止,最后手段。
记住这个顺序:先quit,再wait,最后才考虑terminate。
嗯,线程的启动和停止就讲到这里。这些函数看着简单,但用好了能避免很多坑。下次遇到线程卡死的问题,先检查一下你的停止流程对不对。