Qt单写线程是否需要互斥锁?Release模式主线程死循环问题求解
问题根因
Release模式下编译器会开启代码优化,由于主线程中没有修改t.n的逻辑,编译器会直接将t.n的初始值0缓存到CPU寄存器中,后续每次判断t.n==0时只会读取寄存器中的缓存值,不会重新读取内存中的实际值,子线程对t.n的修改对主线程不可见,因此陷入无限循环。Debug模式下优化等级低,不会做变量缓存,所以运行正常。
你之前认为只有一个写入线程不需要互斥锁,但忽略了多线程场景下的共享变量可见性要求,哪怕没有多写冲突,也需要保证跨线程的修改能被其他线程感知到。另外你的代码中m同样是跨线程读写的共享变量,也存在同样的可见性风险。
解决方案
这里提供3种可行方案,优先推荐方案1,符合C++多线程编码规范:
方案1:将共享变量声明为原子类型
直接使用Qt提供的QAtomicInt或者C++标准库的std::atomic<int>,原子类型天生保证跨线程的操作可见性和原子性,不需要额外加锁。
修改后的代码示例:
#include<QApplication> #include<QThread> #include<QAtomicInt> // 引入原子类型头文件 class MyThread :public QThread { Q_OBJECT public: QAtomicInt n = 0; // 替换原int n QAtomicInt m = 1; // 替换原int m,规避同样的可见性问题 void run()override; }; void MyThread::run() { for (; m.load();) { // 原子变量读取调用load()方法 QThread::msleep(3000); n.store(1); // 原子变量写入调用store()方法 } } int main(int argc, char*argv[]) { QApplication a(argc, argv); MyThread t; t.start(); for (; 1;) { if (t.n.load() != 0) { break; } } t.m.store(0); t.quit(); t.wait(); return 0; } #include"main.moc"
方案2:添加编译器内存屏障
如果不想修改变量类型,可以在主线程的判断逻辑前加内存屏障,强制编译器每次都重新读取内存中的变量值,禁止优化变量缓存:
int main(int argc, char*argv[]) { QApplication a(argc, argv); MyThread t; t.start(); for (; 1;) { // 内存屏障,强制编译器刷新变量读取逻辑 asm volatile("" : : : "memory"); if (t.n != 0) { break; } } asm volatile("" : : : "memory"); t.m = 0; t.quit(); t.wait(); return 0; }
注意:该方案依赖编译器对内联汇编的支持,可移植性低于方案1。
方案3:使用Qt信号槽做跨线程通知
Qt的信号槽天然支持跨线程通信,不需要手动处理变量可见性问题,是Qt框架下更符合设计规范的实现方式,同时也解决了原代码自定义死循环占满CPU核心的问题:
#include<QApplication> #include<QThread> class MyThread :public QThread { Q_OBJECT public: int m = 1; void run()override; signals: void exitNotify(); // 定义退出通知信号 }; void MyThread::run() { for (; m;) { QThread::msleep(3000); emit exitNotify(); // 触发退出通知 } } int main(int argc, char*argv[]) { QApplication a(argc, argv); MyThread t; // 绑定信号触发后的退出逻辑 QObject::connect(&t, &MyThread::exitNotify, &a, [&](){ t.m = 0; t.quit(); t.wait(); a.exit(0); }); t.start(); return a.exec(); // 使用Qt原生事件循环替代自定义死循环 } #include"main.moc"
内容的提问来源于stack exchange,提问作者kenash0625
相关产品推荐
相关产品推荐

