【发布时间】:2016-03-01 14:04:13
【问题描述】:
为了更新读取缓存,我需要一个非常快速(在某种意义上“读取器成本低”,而不是“低延迟”)更改通知机制:
情况
线程 W (Writer) 只偶尔更新一次数据结构 (S)(在我的例子中是地图中的设置)。
线程R (Reader) 维护S 的缓存并且经常读取它。当 Thread W 更新 S Thread R 需要在合理的时间(10-100ms)通知更新。
架构是 ARM、x86 和 x86_64。我需要使用 gcc 4.6 及更高版本支持 C++03。
代码
是这样的:
// 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);
我的问题
在线程R 中,“快速路径”中没有发生锁定或障碍(没有可用的更新)。
这是一个错误吗?这种设计的后果是什么?
我是否需要将updateAvailable 限定为volatile?
R 会得到更新最终吗?
我目前的理解
数据一致性安全吗?
这看起来有点像“双重检查锁定”。根据http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,可以使用内存屏障在 C++ 中修复它。
然而,这里的主要区别是共享资源永远不会在 Reader 快速路径中被触及/读取。更新缓存时,由互斥体保证一致性。
R 会收到更新吗?
这就是棘手的地方。据我了解,运行线程R 的CPU 可以无限期地缓存updateAvailable,有效地将读取方式移动到实际if 语句之前。
因此更新可能会持续到下一次缓存刷新,例如当另一个线程或进程被调度时。
【问题讨论】:
-
代码有未定义的行为,句号。添加
volatile不会改变这一点。 -
你能详细说明一下吗?我看到它未定义的唯一方法是如果 CPU 不能可靠地将存储传播到其他内核。
volatile应确保编译器实际上向内存地址发出存储(无论 CPU 决定如何处理,都是另一回事...... - 它可以保存在写入队列中、缓存中或到达主内存之前的任何东西中) -
对另一个线程同时修改的变量的非原子读取是未定义的行为。添加
volatile根本不会改变这一点。标准对此很清楚,违反该规则的是UB,因此您无法预测编译器将对您的代码做什么。该标准没有提及 CPU 或其他内核,它说在这种情况下确保正确行为的唯一方法是使用原子操作。 isvolatileusefulwiththreads.com -
@JonathanWakely 这就是我陈述架构并使用编译器的原因。我可以轻松地检查我的代码并提供通用替代方案,该替代方案对于实现未知的环境而言性能较低。标准中未定义的东西可以很好地为特定的架构和编译器定义。
标签: c++ multithreading c++03 lock-free