3

以下比较是原子动作吗?即,它可以简化为单个 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();
}
4

8 回答 8

7

这是错误的做法。

您的主线程正在尽可能快地消耗 CPU 周期,除了等待flag达到零之外什么都不做。每次尝试此测试都会失败,最后一次除外。不要以这种方式执行此操作,而是使用您的线程对象最有可能必须使主线程暂停自身的“连接”功能,直到所有工作线程完成。

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

于 2011-04-24T19:56:49.993 回答
2

没有任何 C++ 操作保证是原子操作。

于 2011-04-24T19:53:10.523 回答
2

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

于 2011-04-24T19:53:30.293 回答
2

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

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

如果您想要适当的保证, Windows 上有各种互锁函数和gcc 上的原子内置函数。

于 2011-04-24T19:57:15.890 回答
1

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

如果变量标记为 volatile,您可能会安全地进行简单测试,但这将是特定于平台的。正如其他人指出的那样,C++ 本身对此不做任何保证。

于 2011-04-24T19:55:17.563 回答
1

没有

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

但是,因为您正在比较相等性,所以_InterlockedCompareExchangeinstrinsic(用于lock cmp xchgx86 中的指令)可能会满足您的需求,尽管它会涉及替换数据。

于 2011-04-24T19:55:21.587 回答
1

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

于 2011-04-24T19:52:21.117 回答
0

您应该问的问题是“是原子的”吗?这就是这里的一切。当 flag 达到 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 (&flag, 1) 这将自动递减标志。

最后,在睡眠中旋转并不是最好的方法。您想等待一个条件,等待一个信号。当工作线程意识到他们已将标志递减为 0 时,让他们提出条件或信号。

于 2011-04-27T02:02:39.483 回答