当前位置: 首页 > news >正文

Qt 死锁避坑实录:从一段“看起来很 Qt”的代码说起

目录

一、先给结论

二、一段“看起来完全没问题”的死锁代码

1.场景设定

2.死锁复现代码(精简版)

3.Worker 实现(死锁核心)

三、为什么直接死锁?(Qt 特有版)

1️. AutoConnection 在这里是 QueuedConnection

2️. 发生了什么?

四、更隐蔽的变种:deleteLater + 锁(Qt 老坑)

五、正确解法一(最推荐):emit 前解锁

原则:信号 = 通知,不是同步调用

六、解法二:明确 QueuedConnection + 拷贝数据(线程安全版)

七、解法三:用 QAtomic(轻量场景)

八、解法四:QObject 线程边界法则(高级)

一个对象只被一个线程“操作”

九、Qt 死锁预防清单

十、最终推荐模板

十一、一句话总结


觉得有用,就请您帮忙点赞转发收藏吧,您的鼓励是我创作的动力,多谢看官。

由于能力水平有限,文中的错误或不严谨的地方在所难免,还请批评指正。

摘要:Qt 多线程里 90% 的死锁都不是“写错了锁”,而是信号槽 + QObject::thread() + 隐式队列锁 + 递归 mutex​ 共同制造出来的。本文用一段真实感极强的 Qt 风格代码复现死锁,拆解 Qt 特有的Event Loop / QueuedConnection / 跨线程 delete / QMutex 递归陷阱,并给出 4 种可落地的工程级解决方案。看完你会明白:为什么QMutexLocker救不了你,deleteLater()反而救了你。


一、先给结论

Qt 中预防死锁,记住这 5 条比背《操作系统死锁四条件》有用:

  1. 锁永远不要跨 event loop 边界持有

  2. QueuedConnection 回调里不拿别的线程的锁

  3. 一个线程 = 一个 QMutex 责任域,不要“共享对象 + 共享锁”

  4. emit signal 前能 unlock 就 unlock

  5. 跨线程 delete / access 对象 → 只用deleteLater()

下面用代码说话。


二、一段“看起来完全没问题”的死锁代码

1.场景设定

  • Worker 在子线程干活

  • MainThread 发命令

  • QMutex保护共享数据

  • 用 signal/slot 通知状态

2.死锁复现代码(精简版)

// data.h struct SharedData { QMutex mutex; int value = 0; }; // worker.h class Worker : public QObject { Q_OBJECT public slots: void doWork(); signals: void progress(SharedData *data); }; // main thread SharedData data; Worker *worker = new Worker; QThread *th = new QThread; worker->moveToThread(th); connect(th, &QThread::started, worker, &Worker::doWork); connect(worker, &Worker::progress, [](SharedData *d){ d->mutex.lock(); // 子线程已经获取锁还在摸鱼没释放呢,主线程又想获取锁,相当于先后调用了两次lock(),程序必死锁 qDebug() << "UI read:" << d->value; d->mutex.unlock(); }); th->start();

3.Worker 实现(死锁核心

void Worker::doWork() { >三、为什么直接死锁?(Qt 特有版)

1️. AutoConnection 在这里是 QueuedConnection

因为:

worker->moveToThread(th);

所以:

emit progress() → Worker(子线程) → MainThread → Qt 帮你 queue 一个 meta call

2️. 发生了什么?

执行顺序:

时间

线程

行为

T1

Worker

mutex.lock()

T2

Worker

emit progress()→ post event 到 main thread

T3

Main

event loop 处理 signal

T4

Main

lambda 里mutex.lock()阻塞

T5

Worker

等 main thread 处理完 signal

T6

Worker

永远等不到 mutex

经典死锁四条件 Qt 版达成

  • 互斥

  • 占有且等待 (子线程占锁等 main)

  • 不可抢占 (QMutex 默认)

  • 循环等待 (main ↔ worker)


四、更隐蔽的变种:deleteLater + 锁(Qt 老坑)

void Worker::doWork() { QMutexLocker locker(&data->mutex); >五、正确解法一(最推荐):emit 前解锁

原则:信号 = 通知,不是同步调用

void Worker::doWork() { { QMutexLocker locker(&data->mutex); >六、解法二:明确 QueuedConnection + 拷贝数据(线程安全版)
signals: void progress(int value); // 简单数据,传值,不传指针
void Worker::doWork() { int v; { QMutexLocker locker(&data->mutex); >七、解法三:用 QAtomic(轻量场景)
QAtomicInt value; void Worker::doWork() { emit progress(value.fetchAndAddRelaxed(10) + 10); }

无锁

不适合复杂结构


八、解法四:QObject 线程边界法则(高级)

一个对象只被一个线程“操作”

class Worker : public QObject { Q_OBJECT QMutex mutex; public: void safeAdd(); };

规则:

  • Worker 永远只在自己线程操作 mutex

  • UI 想读?→ signal / invokeMethod(Queued)

QMetaObject::invokeMethod(worker, []{ worker->safeAdd(); // ✅ 在自己线程跑 }, Qt::QueuedConnection);

本质:用 event loop 当锁


九、Qt 死锁预防清单

1️.QMutex 只保护数据,不保护逻辑​

2️.emit signal 时,手里不能握着任何线程的锁​

3️.AutoConnection 是 Qt 给你埋的雷,跨线程就当它是 Queued​

4️.deleteLater() 是 Qt 线程安全最后的尊严​

5️.event loop 能解决的同步,就别用 mutex


十、最终推荐模板

void Worker::doWork() { // 1. 锁最小作用域 int snapshot; { QMutexLocker locker(&m_mutex); m_data.value += 1; snapshot = m_data.value; } // 2. emit 无锁 emit progress(snapshot); // 3. 耗时操作 heavyTask(); }

十一、一句话总结

Qt 的死锁,大多不是多线程问题,而是你以为 Qt 帮你串行化了逻辑,其实它只是帮你排了个队

真正的 Qt 并发安全,不是“锁住一切”,而是让对象只活在一个线程里,剩下的交给 signal / event loop / deleteLater

http://www.jsqmd.com/news/1392556/

相关文章:

  • AI 开发的下一个分水岭:从「调模型」到「调度模型」
  • IEEE期刊论文精读与完整复现 | F-WMMSE:让天线位置参与波束成形,和速率提升约20%
  • 3.Java变量与数据类型
  • 长输供热难点攻克|61.7km超长热力管线,亚控KingSCADA搭建全冗余智慧调度系统
  • 2026优质承压设备集成公司哪家好?实力靠谱承压设备集成商汇总 - 2027品牌AI展
  • 合肥巢湖市雨天窗台渗水别乱修!我的无拆窗维修全过程记录 - 趣闻早乐评
  • 国内锂电工业烤箱、注液机、转轮除湿机、NMP 回收系统靠谱厂家推荐,泽润机械为珠三角源头首选大厂 - 变量人生001
  • B样条曲线计算
  • 第6章 时域递归滤波:IIR滤波器在视频降噪中的应用
  • 2026微信小程序开发费用需要多少?哪个更具性价比?
  • 从零到一带你速通DeepSeek Harness
  • 极空间部署OneNav实战:书签分类、数据保存与公网访问
  • Easy Image X v3 26X4 深度解析:步进式装机、部署预设与无人值守直达桌面
  • 沃尔玛购物卡回收平台怎么选?看折扣与到账两个指标 - 圆圆收
  • 2026不锈钢飘带雕塑厂家选购指南及实力参考 - 曲阳嘉华园林
  • DWMBlurGlass窗口毛玻璃美化完全指南:4种材质怎么选,Win10/11标题栏一步到位的界面美化方案
  • 重庆宠物医院助理医生岗前培训 2026 想学宠物临床实操去哪里 - 湖北找学校
  • 长沙AI大模型开发培训行业评测 正规机构课程与师资配置整理 - 梦想蓝途
  • 2026年头盔安全插扣源头厂家供应商推荐:批量采购选型指南 - 汇聚至此
  • 【数据结构】二叉树的存储结构(顺序/链式)
  • 那五个永远调不对大小的窗口,终于有救了:Window Resizer 强制调整窗口大小实操指南
  • 2026年地坪漆十大品牌选型全指南:正规合规服务商盘点、多场景适配解析及签约避坑实用FAQ - 商业大观
  • 破解物理AI技术困局(37):TVA攻克多智能体协同难题
  • 多智能体应用实战 | Agent不是玩具,是生产力——OpenClaw六大企业应用场景与落地价值
  • 瓦努阿图绿卡到底靠不靠谱?这几类人办了才不亏 - 优企甄选
  • 黑苹果EFI配置从8小时缩到50分钟:OpCore-Simplify如何把复杂留给自己
  • 笔试面试一站式,考编上岸选博傲 - 博傲教育
  • 2026广东建筑资质办理选购指南|6家服务商测评+高驳回率破局方案+六大避坑要点 - 优质品牌中立测评推荐
  • 资源编号:339 | 高德地图 9.1.87(车机定制版)
  • 2026地坪行业核心品牌实力对比全解析:资质/产能/案例/服务多维度选型指南+合作避坑FAQ - U渠道