// 线程不安全demo
// roots 是const成员函数,那就表示它是一个读操作。在没有同步的情况下,让多个线程执行读操作是安全的
// 但本例没线程安全,线程中的一个或两个尝试修改成员变量rootsAreValid和rootVals,在没有同步的情况下,不同线程读写相同内存,这就是数据竞争
class Polynamial {
public:
using Rootstype = std::vector<double>;
Rootstype roots() const {
if (!rootsAreValid) {
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable Rootstype rootVals{};
};
// 对于多个线程不安全的变量,最简单的方式是使用mutex(互斥量)
// std::mutex 既不可移动,又不可复制,因此包含他们的类也是不可移动和不可复制的
class Polynamial {
public:
using Rootstype = std::vector<double>;
Rootstype roots() const {
std::lock_guard<std::mutex> g(m);
if (!rootsAreValid) {
rootsAreValid = true;
}
return rootVals;
}
private:
mutable std::mutex m;
mutable bool rootsAreValid{ false };
mutable Rootstype rootVals{};
};
// std::atomic 通常是一个开销更小的方法
// std::automic 既不可移动也不可复制,因此包含它们的类也是不可移动和不可复制的
class Point {
public:
double distance() const noexcept {
++callCount;
return std::sqrt((x*x) + (y*y));
}
private:
mutable std::stomic<unsigned> callCount{0};
double x,y;
};
// 你可能会过度依赖std::automic,尝试使用一对std::automic 变量而不是互斥量
// 此处第一个线程调用magicValue, cacheValue视为false,而第二个线程调用magicValue已将cacheValid视为false,因此线程二执行线程一的计算,线程不安全
class Weight{
public:
int magicValue() const
{
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cachedValue = val1 + val2;
cacheValid = true;
return cachedValid;
}
}
private:
mutable std::automic<bool> cacheValid{false};
mutable std::automic<int> cachedValue;
};
// 对于需要两个以上的变量和内存位置作为一个单元操作的话,就应该使用互斥量 std::mutex
// 可以肯定的是,const成员函数应支持并发执行【理论读】,这就是为什么你应该确保const成员函数是线程安全的
class Widget {
public:
int magicValue() const
{
std::lock_guard<std::mutex> guard(m); //锁定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};
}