C++ 11中有什么方法可以为对象实现无锁缓存,从多个线程可以安全地访问它吗?我要缓存的计算并不是很便宜,但也不是太昂贵,因此在我的情况下,需要使用锁才能达到缓存的目的。 IIUC,不能保证std::atomic
是无锁的。
编辑:由于计算不是太昂贵,我实际上不介意它运行一次或两次太多。但是,我需要确保所有消费者都获得正确的值(value)。在下面的幼稚示例中,这不能保证,因为由于内存重新排序,一个线程可能会获得未初始化的m_val
值,因为另一个线程将m_alreadyCalculated
设置为true,但尚未设置m_val
的值。
Edit2:下面的注释指出,对于基本类型,std::atomic
可能是无锁的。如果是这样,在下面的示例中使用C++ 11的内存排序以确保在设置m_alreadyCalculated
的值之前不可能将m_val
设置为true的正确方法是什么?
非线程安全的高速缓存示例:
class C {
public:
C(int param) : m_param(param) {}
getValue() {
if (!m_alreadyCalculated) {
m_val = calculate(m_param);
m_alreadyCalculated = true;
}
return m_val;
}
double calculate(int param) {
// Some calculation
}
private:
int m_param;
double m_val;
bool m_alreadyCalculated = false;
}
最佳答案
考虑如下:
class C {
public:
double getValue() {
if (alreadyCalculated == true)
return m_val;
bool expected = false;
if (calculationInProgress.compare_exchange_strong(expected, true)) {
m_val = calculate(m_param);
alreadyCalculated = true;
// calculationInProgress = false;
}
else {
// while (calculationInProgress == true)
while (alreadyCalculated == false)
; // spin
}
return m_val;
}
private:
double m_val;
std::atomic<bool> alreadyCalculated {false};
std::atomic<bool> calculationInProgress {false};
};
它实际上不是无锁的,里面有一个自旋锁。但是我想如果您不想由多个线程运行
calculate()
,就无法避免这种锁定。getValue()
在这里变得更加复杂,但是重要的部分是,一旦计算出m_val
,它将始终在第一个if
语句中立即返回。更新
出于性能原因,将整个类填充到缓存行大小也是一个好主意。
更新2
原始答案中有一个错误,感谢JVApen指出这一点(用注释标记)。最好将变量
calculationInProgress
重命名为calculationHasStarted
。另外,请注意,此解决方案假定
calculate()
不引发异常。