线程之间的更改通知标志是否需要内存屏障?

Han*_* S. 6 c++ multithreading lock-free c++03

我需要一个非常快(在某种意义上"读取器的低成本",而不是"低延迟")线程之间的更改通知机制,以便更新读取缓存:

情况

线程W(Writer)S仅在一段时间内更新数据结构()(在我的情况下是地图中的设置).

线程R(Reader)维护缓存S并且经常读取它.当线程W更新S线程R需要在合理的时间(10-100ms)内通知更新.

架构是ARM,x86和x86_64.我需要支持C++03gcc 4.6及更高版本.

是这样的:

// variables shared between threads
bool updateAvailable;
SomeMutex dataMutex;
std::string myData;

// variables used only in Thread R
std::string myDataCache;

// Thread W
SomeMutex.Lock();
myData = "newData";
updateAvailable = true;
SomeMutex.Unlock();

// Thread R

if(updateAvailable)
{
    SomeMutex.Lock();
    myDataCache = myData;
    updateAvailable = false;
    SomeMutex.Unlock();
}

doSomethingWith(myDataCache);
Run Code Online (Sandbox Code Playgroud)

我的问题

在线程R中,"快速路径"中没有发生锁定或障碍(无可用更新).这是一个错误吗?这个设计有什么后果?

我需要有资格updateAvailablevolatile

最终R得到更新吗?

到目前为止我的理解

数据一致性是否安全?

这看起来有点像"Double Checked Locking".根据http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,可以使用内存屏障在C++中修复它.

但是,这里的主要区别是在Reader快速路径中永远不会触摸/读取共享资源.更新缓存时,互斥锁保证一致性.

R得到更新吗?

这是它变得棘手的地方.据我所知,运行Thread的CPU R可以updateAvailable无限期地缓存,有效地在实际if语句之前移动Read方式.

因此,更新可能会持续到下一次缓存刷新,例如,当调度另一个线程或进程时.

Tsy*_*rev 1

我需要符合资格updateAvailablevolatile

由于volatile与 C++ 中的线程模型无关,因此您应该使用原子来使您的程序严格符合标准:

一个C++11或更新的更好的atomic<bool>方法是与memory_order_relaxed存储/加载一起使用:

atomic<bool> updateAvailable;

//Writer
....
updateAvailable.store(true, std::memory_order_relaxed); //set (under mutex locked)

// Reader

if(updateAvailable.load(std::memory_order_relaxed)) // check
{
    ...
    updateAvailable.store(false, std::memory_order_relaxed); // clear (under mutex locked)
    ....
}
Run Code Online (Sandbox Code Playgroud)

自 4.7 起,gcc 在其原子内置函数中支持类似的功能。

至于gcc 4.6,似乎在访问变量时没有严格确认的方法来逃避围栏updateAvailable。实际上,内存栅栏通常比 10-100ms 的时间快得多。所以你可以使用它自己的原子内置函数

int updateAvailable = 0;

//Writer
...
__sync_fetch_and_or(&updateAvailable, 1); // set to non-zero
....

//Reader
if(__sync_fetch_and_and(&updateAvailable, 1)) // check, but never change
{
    ...
    __sync_fetch_and_and(&updateAvailable, 0); // clear
    ...
}
Run Code Online (Sandbox Code Playgroud)

数据一致性安全吗?

是的,它很安全。你的理由在这里绝对正确:

在 Reader 快速路径中永远不会触及/读取共享资源。


这不是双重检查锁定!

问题本身已经明确说明了这一点。

如果updateAvailable为 false,则Reader线程使用myDataCache该线程本地的变量(没有其他线程使用它)。通过双重检查锁定方案,所有线程都直接使用共享对象。

为什么这里不需要内存栅栏/屏障

唯一同时访问的变量是updateAvailablemyData变量是通过互斥锁保护来访问的,它提供了所有需要的栅栏。myDataCache是Reader 线程本地的。

当 Reader 线程看到updateAvailable变量为false时,它​​会使用变量,该变量由线程本身myDataCache更改。在这种情况下,程序顺序保证了更改的正确可见性。

至于变量的可见性保证updateAvailable,C++11标准即使没有栅栏也为原子变量提供了这样的保证。29.3 p13 说:

实现应该使原子存储在合理的时间内对原子加载可见。

乔纳森·韦克利 (Jonathan Wakely) 已确认,本段内容甚至适用于聊天中的memory_order_relaxed访问。

  • 首先,挥发性在ARM上不会以这种方式工作。时期。由于严格的订购保证,它可以在 Intel 上运行,而 ARM 上不提供这种保证。其次,原子确实仍然存在于 C++ 之外 - 请参阅我的回答。 (3认同)