我正在尝试使用C ++ 0x支持并且存在一个问题,我想不应该存在。要么我不理解这个主题,要么gcc有一个bug。
我有以下代码,最初x
和y
相等。线程1始终先递增x
,然后递增y
。两者都是原子整数值,因此根本没有增量问题。线程2正在检查x
是否小于y
,如果是,则显示错误消息。
此代码有时失败,但为什么?这里的问题可能是内存重新排序,但默认情况下所有原子操作都是顺序一致的,我没有明确放松那些操作。我正在x86上编译这段代码,据我所知,该代码不应该有任何订购问题。你能解释一下问题是什么吗?
#include <iostream>
#include <atomic>
#include <thread>
std::atomic_int x;
std::atomic_int y;
void f1()
{
while (true)
{
++x;
++y;
}
}
void f2()
{
while (true)
{
if (x < y)
{
std::cout << "error" << std::endl;
}
}
}
int main()
{
x = 0;
y = 0;
std::thread t1(f1);
std::thread t2(f2);
t1.join();
t2.join();
}
可以查看结果here。
答案 0 :(得分:12)
比较存在问题:
x < y
子表达式(在本例中为x
和y
)的评估顺序未指定,因此可以在y
或{{1}之前评估x
可以在x
之前评估。
如果先读取y
,则表示您遇到问题:
x
如果您明确确保首先阅读x = 0; y = 0;
t2 reads x (value = 0);
t1 increments x; x = 1;
t1 increments y; y = 1;
t2 reads y (value = 1);
t2 compares x < y as 0 < 1; test succeeds!
,则可以避免此问题:
y
答案 1 :(得分:11)
问题可能出在你的测试中:
if (x < y)
该主题可以评估x
,并且在很晚之后不会评估y
。
答案 2 :(得分:4)
每隔一段时间,x
将在y
回绕到零之前回绕到0。此时y
合法地大于x
。
答案 3 :(得分:-3)
首先,我同意“Michael Burr”和“James McNellis”。你的考试不公平,合法的失败可能性。然而,即使您按照“James McNellis”的方式重写测试,也表明测试可能会失败。
这样做的第一个原因是你没有使用volatile
语义,因此编译器可能会对你的代码进行优化(在单线程的情况下应该是可以的)。
但即使使用volatile
,您的代码也无法保证正常运行。
我认为你并不完全理解内存重新排序的概念。实际上,内存读/写重新排序可以在两个级别进行:
使用volatile
会阻止(1)。但是你没有做任何事情来阻止(2) - 硬件的内存访问重新排序。
为了防止出现这种情况,你应该在代码中添加特殊的内存栅栏指令(指定用于CPU,不像volatile
那样仅用于编译器。)
在x86 / x64中有许多不同的内存栅栏指令。此外,默认情况下,每个具有lock
语义的指令都会发出完整的内存栅栏。
此处提供更多信息: