用std::mutex或std::atomic保证const成员函数线程安全¶
假设有一个表示多项式的类,它包含一个返回根的 const 成员函数:
class Polynomial {
public:
std::vector<double> roots() const
{ //@ 实际仍需要修改值,所以将要修改的成员声明为mutable
if (!rootsAreValid)
{
… //@ 计算根
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable std::vector<double> rootVals{};
};
假如此时有两个线程对同一个对象调用成员函数,虽然函数声明为 const,但由于函数内部修改了数据成员,就可能产生数据竞争。最简单的解决方法是引入一个 std::mutex:
class Polynomial {
public:
std::vector<double> roots() const
{
std::lock_guard<std::mutex> l(m);
if (!rootsAreValid)
{
… //@ 计算根
rootsAreValid = true;
}
return rootVals;
}
private:
mutable std::mutex m; //@ std::mutex是move-only类型,因此这个类只能移动不能拷贝
mutable bool rootsAreValid{ false };
mutable std::vector<double> rootVals{};
};
对一些简单的情况,使用原子变量 std::atomic 可能开销更低(取决于机器及 std::mutex 的实现):
class Point {
public:
double distanceFromOrigin() const noexcept
{
++callCount; //@ 计算调用次数
return std::sqrt((x * x) + (y * y));
}
private:
mutable std::atomic<unsigned> callCount{ 0 }; //@ std::atomic也是move-only类型
double x, y;
};
因为 std::atomic 的开销比较低,很容易想当然地用多个原子变量来同步:
class A {
public:
int f() const
{
if (flag) return res;
else
{
auto x = expensiveComputation1();
auto y = expensiveComputation2();
res = x + y;
flag = true; //@ 设置标记
return res;
}
}
private:
mutable std::atomic<bool> flag{ false };
mutable std::atomic<int> res;
};
这样做可行,但如果多个线程同时观察到标记值为 false,每个线程都要继续进行运算,这个标记反而没起到作用。先设置标记再计算可以消除这个问题,但会引起一个更大的问题:
class A {
public:
int f() const
{
if (flag) return res;
else
{
flag = true; //@ 在计算前设置标记值为true
auto x = expensiveComputation1();
auto y = expensiveComputation2();
res = x + y;
return res;
}
}
private:
mutable std::atomic<bool> flag{ false };
mutable std::atomic<int> res;
};
假如线程1刚设置好标记,线程2此时正好检查到标记值为 true 并直接返回数据值,然后线程1接着计算结果,这样线程2的返回值就是错的。
因此如果要同步多个变量或内存区,最好还是使用 std::mutex。
class A {
public:
int f() const
{
std::lock_guard<std::mutex> l(m);
if (flag) return res;
else
{
auto x = expensiveComputation1();
auto y = expensiveComputation2();
res = x + y;
flag = true;
return res;
}
}
private:
mutable std::mutex m;
mutable bool flag{ false };
mutable int res;
};