比较是原子操作吗?

时间:2011-04-24 19:48:22

标签: c++ multithreading mutex atomic operation

以下比较是原子动作吗?即,它可以简化为单个CPU指令吗?

char flag = 2;

for(;;)
{
    if (!flag) // <-- this
        break;

    // sleep
}

这就是我正在做的事情:

int main()
{
    sf::Mutex Mutex;
    char flag = 2;

    coordinatorFunction(flag);

    for(;;)
    {
        if (!flag)
            break;

        // sleep
    }
}

void workerFunction(void* a)
{
    char* p = static_cast<char*>(a);

    // work

    GlobalMutex.Lock();
    --*p;
    GlobalMutex.Unlock();
}

void coordinatorFunction(char& refFlag)
{
    sf::Thread worker1(&workerFunction, &refFlag);
    sf::Thread worker2(&workerFunction, &refFlag);

    worker1.Launch();
    worker2.Launch();
}

8 个答案:

答案 0 :(得分:7)

这是错误的做法。

你的主线程正在尽可能快地烧掉CPU周期,除了等待flag达到零之外什么都不做。除了最后一次测试外,每次尝试都会失败。不要以这种方式进行,而是使用您的线程对象最有可能使主线程暂停的“连接”工具,直到所有工作人员完成为止。

这样,并非巧合,你不会在乎测试是否是原子的,因为你根本不需要它。

答案 1 :(得分:2)

C ++操作都不能保证是原子操作。

答案 2 :(得分:2)

在C ++中,没有任何东西可以保证是原子的。

答案 3 :(得分:2)

没有。据我所知,C ++不保证什么是原子的,什么不是 - 这是特定于平台的。即使您的平台保证可以原子方式进行比较,也不能保证您的C ++编译器会选择该指令。

然而,在实践中,简单值类型(如char,int,float等)的比较可能是原子的。但是,无论是在编译器级别还是在处理器级别,您仍然需要了解可能的指令重新排序。在这种情况下,这可能无关紧要,但在一般情况下,它可以并且确实如此。您还需要注意,即使比较是比较然后分支也不是原子的 - 所以如果您尝试使用标志来管理该访问,则2个线程都可以输入相同的代码块。

如果您需要适当的保证,Windows上有Interlocked functions种,gcc上有atomic builtins

答案 4 :(得分:1)

不,C ++不保证任何操作都是原子的。你的问题中的代码很可能被编译成从内存到寄存器的加载,这可能本身需要多个指令,然后进行测试。

答案 5 :(得分:1)

比较不是原子的,因为它需要多个机器语言指令来执行它(从内存加载到寄存器等)。由于内存模型的灵活性和缓存,执行测试的线程可能无法“看到”结果另一个线程马上。

如果变量标记为volatile,那么您可以安全地执行简单测试,但这将是特定于平台的。正如其他人所说,C ++本身并不能保证这一点。

答案 6 :(得分:1)

比较涉及读取两个数据以及执行实际比较。 数据可以在读取和比较指令之间发生变化,因此它不是原子的。

但是,因为您要比较相等性,_InterlockedCompareExchange instrinsic(适用于x86中的lock cmp xchg指令)可能会满足您的需求,但它会涉及替换数据。

答案 7 :(得分:0)

你应该问的问题是“是 - 原子”吗?多数民众赞成在这里。你想在旗帜达到0时做点什么。

您不关心这种情况:

1. Main thread reads flag, and it is 1.
2. Worker changes flag with --
3. Main thread doesn't see that flag is actually 0.

因为在1 ns内,主线程循环并再次尝试。

你关心的是 - 不是原子的,同时改变它的两个线程会跳过减量:

1. Thread A reads flag, flag is 2
2. Thread B reads flag, flag is 2
3. Thread A decrements its copy of flag, 2, and writes to flag, flag is 1
4. Thread B decrements its copy of flag, also 2, and writes to flag, flag is 1.

你输了一个减量。你想使用__sync_fetch_and_sub(&amp; flag,1),它将原子地递减标志。

最后,围绕睡眠旋转并不是最好的方法。你想要等待一个条件,等待signal。当工作线程意识到它们已经将标志递减到0时,让工作线程提高条件或信号。