配置:调试结果:程序退出代码为0.
配置:释放结果:主线程无限循环,不会跳出循环(t)。N ==0 = true)。
如何跳出循环?
只有一个写线程,所以我没有使用任何互斥锁。
qt5.13 vs2017
main.cpp:
//#include"QtTestProg.h"
#include<QApplication>
#include<QMessageBox>
#include<QThread>
class MyThread :public QThread
{
Q_OBJECT
public:
int n = 0, m = 1;
void run()override;
};
void MyThread::run()
{
for (; m;) {
QThread::msleep(3000);
n = 1;
}
}
int main(int argc, char*argv[])
{
QApplication a(argc, argv);
MyThread t;
t.start();
for (; 1;)
{
if (t.n != 0)
{
break;
}
}
t.m = 0;
t.quit();
t.wait();
return 0;
}
#include"main.moc"
这个循环:
for (; 1;)
{
if (t.n != 0)
{
break;
}
}
从技术上讲,即使编译器看到t.n
没有被修改,上述代码也可以永远运行。理论上,它可以将值缓存到寄存器中或将其优化出来。在进行无锁编程时,还存在缓存一致性问题。
三个选项:
使用std::atomic在单个变量上具有快速的类似锁的线程安全语义。
在
t.n
的赋值和求值过程中使用std::互斥锁和std::锁。使用一个条件变量,使主线程等待
n
变化,直到运行线程发出信号表明它已经改变。
只有一个写线程,所以我没有使用任何互斥锁。
这是未定义行为。如果从不同的线程读写相同的内存位置,则需要同步。从https://en.cppreference.com/w/cpp/language/memory_model
当一个表达式的求值写入内存位置,而另一个求值读取或修改相同的内存位置时,这两个表达式被称为冲突。有两个相互冲突的求值的程序具有数据争用,除非
- 两个求值在同一个线程或同一个信号处理程序中执行,或者
- 两个冲突的求值都是原子操作(参见std::atomic),或者
- 一个冲突的计算发生在另一个之前(参见std::memory_order)
如果发生了数据争用,程序的行为是未定义的。
你要么需要一个互斥锁来保护读和写,要么使用std::atomic_int
/QAtomicInt
来代替m
和n
的普通int
。
至于未定义行为的后果,在这里(godbolt),您可以看到O2级别的gcc在main()
中完全编译出循环,除非您将TYPE
从顶部的int
更改为std::atomic_int
。
您需要使用QReadWriteLock
。这个类是为您的情况而设计的,当您需要几个读取器而不相互阻塞时。