const 成员函数在外部看来是只读的,所以被认为是线程安全的,然而如果有 mutable 数据成员时,需要保证它的线程安全性。
如果要计算一个成员函数被调用的次数,使用 std::atomic 类型的计数器是一种成本较低的途径:
class Point {
public:
...
double distanceFromOrigin() const noexcept {
++callCount; // 原子性的自增操作
return std::sqrt((x * x) + (y * y));
}
private:
mutable std::atomic<unsigned> callCount{0};
double x, y;
};如果某类需要缓存计算开销较大的 int 类型的变量,则应该尝试使用一对 std::atomic 类型的变量:
class Widget {
public:
...
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = val1 + val2'
cacheValid = true;
return cachedValue;
}
}
private:
mutable std::atomic<bool> cacheValid{false};
mutable std::atomic<int> cachedValue;
};考虑以下情况:
- 一个线程调用
Widget::magicValue时,观察到cacheValid为false,于是执行了两个开销大的计算,并赋值给cachedValue。 - 与此同时,另一个线程也调用
Widget::magicValue,观察到cacheValid值为false,于是也执行了两个开销大的计算。
这样就失去了缓存的意义。也可以颠倒 cacheValid 和 cachedValue 的赋值顺序,但这样也会造成求值错误。
对于单个要求同步的变量或内存区域,使用 std::atomic 就足够了。但是如果有两个或更多的变量或内存区域需要作为一整个单位进行操作时,就要使用互斥量了。
对于 Widget::magicValue 而言,代码应该是这样:
class Widget {
public:
...
int magicValue() const {
std::lock_guard<std::mutex> guard(m);
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = val1 + val2'
cacheValid = true;
return cachedValue;
}
}
...
private:
mutable std::mutex m;
mutable int cachedValue;
mutable bool cacheValid{false};
};