Qt 退出崩溃别只怪 delete,线程和对象释放顺序才是重灾区
Qt 项目里有一种 bug 很讨厌:程序跑的时候一切正常,采集、通信、界面刷新都没问题,用户点右上角关闭按钮,啪,崩了。
更尴尬的是,开发机上不一定复现。现场工控机跑了两天,关机前点一下退出,直接弹崩溃报告。最后查下来,问题不在算法,也不在界面,而是线程还没停干净,对象已经开始释放了。
为什么 demo 没问题,项目里就开始炸?
因为 demo 里线程简单,生命周期短,退出路径也干净。真实项目不是这样。一个工业上位机里可能有串口采集线程、TCP 心跳线程、数据库写入线程、日志落盘线程,UI 还在接收它们的信号。程序退出时,如果 QWidget 已经析构了,后台线程还 emit 信号,或者 Worker 里还拿着某个已经释放的配置对象指针,崩溃就很自然。
Qt 对象树不是退出安全的万能药。parent 能帮你释放对象,但它不负责帮你判断线程有没有结束,更不保证业务对象的销毁顺序一定符合你的预期。
项目里我一般会把退出看成一个独立流程,而不是顺手 close 一下窗口。比如线程对象和 Worker 对象,尽量明确绑定释放关系:
connect(worker,&Worker::finished,worker,&QObject::deleteLater);connect(thread,&QThread::finished,thread,&QObject::deleteLater);这两行看着普通,但在项目里很关键。它解决的不是“少写 delete”这么简单,而是让对象在合适的事件循环里释放。跨线程对象最怕的就是:创建在一个线程,释放在另一个线程,平时没事,退出时随机炸。
真正麻烦的不是启动线程,而是停线程。很多人写:
thread->quit();thread->wait();这只能说明你通知线程退出了,不代表 Worker 当前业务已经安全收尾。比如串口正在读,数据库事务还没提交,socket 正在回调。更稳的做法是给 Worker 一个明确的 stop 接口,让它自己停业务,再发 finished。
connect(qApp,&QCoreApplication::aboutToQuit,worker,&Worker::stop);这段代码解决的是退出时机问题。不要等主窗口析构到一半了,才想起来通知线程停。退出信号要早发,资源释放要晚做。
常见坑
第一个坑,是在主窗口析构函数里直接 delete 线程相关对象。窗口析构时,UI 子对象已经在释放,后台线程如果还在发界面更新信号,等于往废墟里投递消息。
第二个坑,是把 Worker 设置 parent 为主窗口。看起来对象树很整齐,实际很危险。Worker 如果 moveToThread 之后,生命周期就不该再被 UI 直接粗暴接管。线程归线程,界面归界面,别用 parent 掩盖设计混乱。
第三个坑,是依赖 disconnect 解决所有问题。disconnect 能断信号,但断不了正在执行的槽函数,也断不了 Worker 内部持有的裸指针。项目大了以后,连接关系不清楚,本身就是隐形债务。
第四个坑,是忽略日志线程、数据库线程这种“边角料”。很多程序退出崩溃不是采集线程炸的,而是最后一条日志还没写完,日志对象先被释放了。
我自己的判断是:Qt 程序退出崩溃,大多不是某一行 delete 写错,而是生命周期设计从一开始就没画边界。哪些对象属于 UI,哪些对象属于线程,谁负责通知停止,谁负责最终释放,这些必须明确。
一个成熟的 Qt 项目,退出流程应该像开机流程一样被认真设计。先停业务,再停线程;先断外设和网络,再释放对象;先让 Worker 自己收尾,再让 QObject 树清场。
