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};
};