内存排序问题

时间:2010-10-25 05:48:44

标签: c++ multithreading c++11 memory-fences

我正在尝试使用C ++ 0x支持并且存在一个问题,我想不应该存在。要么我不理解这个主题,要么gcc有一个bug。

我有以下代码,最初xy相等。线程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

4 个答案:

答案 0 :(得分:12)

比较存在问题:

x < y

子表达式(在本例中为xy)的评估顺序未指定,因此可以在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,您的代码也无法保证正常运行。

我认为你并不完全理解内存重新排序的概念。实际上,内存读/写重新排序可以在两个级别进行:

  1. 编译器可以交换生成的读/写指令的顺序。
  2. CPU可以按任意顺序执行内存读/写指令。
  3. 使用volatile会阻止(1)。但是你没有做任何事情来阻止(2) - 硬件的内存访问重新排序。

    为了防止出现这种情况,你应该在代码中添加特殊的内存栅栏指令(指定用于CPU,不像volatile那样仅用于编译器。)

    在x86 / x64中有许多不同的内存栅栏指令。此外,默认情况下,每个具有lock语义的指令都会发出完整的内存栅栏。

    此处提供更多信息:

    http://en.wikipedia.org/wiki/Memory_barrier