栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

《Effective Modern C++》学习笔记 - Item 16: 确保 const 成员函数线程安全

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《Effective Modern C++》学习笔记 - Item 16: 确保 const 成员函数线程安全

  • 本节作者想表达的 point 是,由于 const 成员函数代表的一般是只读操作,客户端的调用代码有理由在多线程调用中不考虑同步问题。但实际上有时在 const 函数中也需要修改数据成员,此时函数的线程安全性就应该由你来保证。当然,作者也点明了如果你能确保函数是100%不会涉及多线程调用场景,的确没必要考虑这点,然而实际上完全非多线程的应用场景是越来越少的。
  • 假设有一个 Point 类,其中有一个返回到原点距离的函数 distanceFromOrigin,显然它可以是 const 的。现在如果我们有一个需求是要记录该函数的调用次数,就需要在函数中改变某个计数变量的值。解决方法是使用 mutable 关键字声明计数变量,这样的变量可以在 const 函数中被修改。为了保证线程安全,我们还可以用 std::atomic 来封装该计数变量:
class Point {
public:
...
	double distanceFromOrigin() const noexcept {
		++callCount;
		return std::sqrt((x * x) + (y * y));
	}
...
private:
	mutable std::atomic callCount{ 1 };
	double x, y;
};
  • 需要注意,由于 std::atomic 只有移动构造,包含它的 Point 类也只能移动构造而不能用复制构造函数或赋值运算符(=)构造。以下所述的 std::mutex 也有这一性质。当处理多变量时,使用 std::atomic 可能无法解决数据竞争,例如有如下函数:
class Widget {
public:
...
	int magicValue() const {
		if (cachedValue) return cachedValue;
		else {
			auto val1 = expensiveComputation1();	// 某个运算量很大的函数
			auto val2 = expensiveComputation2();	// 同上
			
			cachedValue = val1 + val2;	// 二者谁先谁后?
			cachevalid = true;			// 答案是都不好!
			return cachedValue;
		}
	}
...
private:
	mutable std::atomic cachevalid = false;
	mutable std::atomic  cachedValue;	// 通过缓存避免大计算量的重复计算
  • 仔细思考会发现,cachedValue 和 cachevalid 二者赋值的两种顺序都是不好的:
    1. cachedValue 先赋值,可能当线程1完成大运算量计算向 cachedValue 赋值时,线程2检查 cachevalid 为 false,于是重复计算;
    2. cachevalid 先赋值,可能当线程1完成计算并已向 cachevalid 赋值 true,但还未向 cachedValue 赋值时,线程2检查 cachevalid 为 true,于是直接取走了错误的 cachedValue,结果更糟糕。
  • 正确的做法是不要让两个线程同时进入到这段函数中。std::mutex 是一个信号量(锁),上锁期间只允许一个线程进入,其他线程阻塞。std::lock_guard 可以通过一个 std::mutex 构造,初始化时即上锁,离开当前作用域(大括号范围)自动析构解锁。以上函数重写如下:
class Widget {
public:
...
	int magicValue() const {
		std::lock_guard g(m);

		if (cachedValue) return cachedValue;
		else {
			auto val1 = expensiveComputation1();
			auto val2 = expensiveComputation2();
			
			cachedValue = val1 + val2;
			cachevalid = true;
			return cachedValue;
		}
	}
...
private:
	mutable std::mutex m;
	mutable std::atomic cachevalid = false;
	mutable std::atomic  cachedValue;
};

总结
  1. 确保 const 成员函数线程安全,除非你确定它们永远不会被用在并发场景中。
  2. 使用 std::atomic 或许性能会优于信号量(mutex),但它们仅适用于对单变量的操作。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/675811.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号