根据输出的信息,任何人都可以解释以下代码吗?
(a==1 && a==2 && a==3)
的全部都正确吗?
#include <iostream>
#include <thread>
int a = 0;
int main()
{
std::thread runThread([]() { while (true) { a = 1; a = 2; a = 3; }});
while (true)
{
if (a == 1 && a == 2 && a == 3)
{
std::cout << "Hell World!" << std::endl;
}
}
}
输出:
Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World! Hell World!
...
2019年1月
我认为这个问题与该链接高度相关-> C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?
答案 0 :(得分:10)
您遇到未定义的行为。
您的代码具有竞争条件;一个线程正在读取a
,而另一个线程正在写入,并且没有发生同步。您不得在C ++中执行此操作。可以将执行此操作的程序编译为可以执行任何操作。
实际上,在发行版中,我希望将其编译为if(false)
。编译器优化主线程,不注意同步,证明a没有UB不能是3个不同的值,并优化if
。
在调试版本中,我可以预期到您看到的症状。不是因为它更正确,而是因为调试版本往往不会因这种未定义的行为而跳闸。
因此,要合理地谈论程序,首先要做的就是消除未定义的行为:将a
设为std::atomic<int>
而不是int
。
现在,无论是发行版还是调试版,您都希望看到……正是测试所显示的内容。或者什么都没有。或两者之间的任何东西。结果不再是不确定的,而是不确定的。
if
语句不是原子的。在条件之间,a
可以更改。随着程序永远运行,它有时应该会发生,因为另一个线程正在对其进行更改。
生成的程序定义明确。 Even forward progress guarantees可以,因为您读取了原子变量。
答案 1 :(得分:4)
您可能在禁用优化的情况下编译了程序,因此要编译的目标体系结构的程序集/机器代码实际上按其出现的顺序执行了C ++抽象机中的所有步骤,包括实际存储或加载到/来自RAM。 (调试版本通常将每个变量都视为volatile
。)
在典型的32位int
加载/存储是自然原子的体系结构上,调试版本的行为很像便携式C ++程序,没有使用std::atomic<int> a
和a.store(1, std::memory_order_relaxed)
进行未定义行为(或在x86上,std::memory_order_release
)。
程序将启动一个线程,该线程将a的值反复设置为1、2,然后3(这是runThread行)。
然后,主线程测试a是否等于1,a等于2以及a等于3。如果它等于所有三个,则打印“ hello world”。 (这发生在第二个while(true)
中)
之所以会打印“ hello world”,是因为并发。碰巧一个线程执行了三个测试,而另一个线程恰恰在正确的时间写入了正确的值。请记住,if
的三个部分是一个接一个地完成的
这无处保证。如果您不进行调试构建,则可以对其进行优化。但是考虑到计算机的运行速度以及线程的典型实现,它确实会发生。
请勿依赖此行为。相反,请尝试了解这两个线程同时执行操作。