9

我正在试验 C++0x 支持,但有一个问题,我想不应该存在。要么我不理解这个主题,要么 gcc 有一个错误。

我有以下代码,最初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();
}

结果可以在这里查看。

4

4 回答 4

12

对比有问题:

x < y

子表达式的求值顺序(在本例中为 ofxy)未指定,因此y可以在之前求值,xx可以在 之前求值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首先阅读,则可以避免该问题:

int yval = y;
int xval = x;
if (xval < yval) { /* ... */ }
于 2010-10-25T06:20:25.543 回答
11

问题可能出在您的测试中:

if (x < y)

该线程可以评估x并且y直到很久以后才开始评估。

于 2010-10-25T06:03:19.833 回答
4

时不时地,x会在回绕到零之前y回绕到 0。此时y将合法地大于x

于 2010-10-25T08:16:51.107 回答
-3

首先,我同意“Michael Burr”和“James McNellis”的观点。您的测试不公平,并且有合法的失败可能性。但是,即使您按照“James McNellis”建议的方式重写测试,测试也可能会失败。

第一个原因是您不使用volatile语义,因此编译器可能会对您的代码进行优化(在单线程情况下应该没问题)。

但即使使用volatile您的代码也不能保证正常工作。

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

  1. 编译器可以交换生成的读/写指令的顺序。
  2. CPU 可以以任意顺序执行内存读/写指令。

使用volatile可防止 (1)。但是,您没有采取任何措施来阻止 (2) -硬件对内存访问重新排序。

为防止这种情况,您应该在代码中放置特殊的内存围栏指令(为 CPU 指定,与volatile仅用于编译器不同)。

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

更多信息在这里:

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

于 2010-10-25T07:02:15.323 回答