1

这个问题是在阅读了其他开发人员编写的一些代码后出现的,所以我做了一些研究,发现了 Andrei Alexandrescu 的文章。在他的文章中,他说可以使用 volatile 布尔变量来忙等待(参见第一个示例等待/唤醒)

class Gadget
{
public:
    void Wait()
    {
        while (!flag_)
        {
            Sleep(1000); // sleeps for 1000 milliseconds
        }
    }
    void Wakeup()
    {
        flag_ = true;
    }
    ...
private:
    bool flag_;
};

我真的不明白它是如何工作的。

  1. volatile 不保证操作是原子的。实际上,对布尔变量的读/写是原子的,但理论并不能保证这一点。从我的角度来看,上面的代码可以使用 C++11 安全地重写,方法是使用 std::atomic::load/store 函数和相应的获取/释放内存排序约束。
  2. 在所描述的示例中我们没有这个问题,但是如果我们有不止一个写入,我们可能会遇到内存排序问题。Volatile 不是栅栏,它不强制内存排序,它只是阻止编译器优化。

那么为什么这么多人使用 volatile bool 来忙等待,它真的便携吗?

4

2 回答 2

2

这篇文章并没有说这volatile就是你所需要的(事实上,它不是),只是说它是有用的。

如果你这样做,并且如果你使用简单的泛型组件LockingPtr,你可以编写线程安全的代码,而不必担心竞争条件,因为编译器会为你担心,并会努力指出你错误的地方。

于 2013-11-12T15:51:15.380 回答
0

我真的不明白它是如何工作的。

它依赖于两个假设:

  • 对布尔变量的读写是原子的;
  • 所有线程都有统一的内存视图,因此对一个线程所做的修改将在短时间内对其他线程可见,而无需显式内存屏障。

第一个可能会坚持任何健全的架构。第二个适用于任何单核架构,也适用于当今广泛使用的多核架构,但不能保证它会在未来继续存在。

上面的代码可以通过使用 C++11 安全地重写std::atomic

今天,它可以而且应该是。2001年,写这篇文章的时候,没有那么多。

如果我们有不止一个写入,我们可能会遇到内存排序问题

的确。如果此机制用于与其他数据同步,那么我们依赖第三个假设:保留修改顺序。同样,大多数流行的处理器都会提供这种行为,但不能保证这种行为会继续下去。

为什么这么多人使用 volatile bool 来忙等待

因为他们不能或不会改变在 C++ 获得多线程内存模型之前形成的习惯。

它真的便携吗?

不。C++11 内存模型不能保证这些假设中的任何一个,而且随着典型内核数量的增长,它们很有可能对于未来的硬件支持变得不切实际。volatile从来都不是线程同步的解决方案,而且现在该语言确实提供了正确的解决方案。

于 2013-11-12T16:35:20.740 回答