我有以下函数,旨在“memoize”无参数函数.意思是只调用一次函数,然后在其他时间返回相同的结果.
- private static Func<T> Memoize<T>(Func<T> func)
- {
- var lockObject = new object();
- var value = default(T);
- var inited = false;
- return () => {
- if (inited)
- return value;
- lock (lockObject) {
- if (!inited) {
- value = func();
- inited = true;
- }
- }
- return value;
- };
- }
我可以确定如果一个线程在锁外读取“inited == true”,那么它将读取在“inited”设置为true之前写入的“值”吗?
注意:Double-checked locking in .NET涵盖了它应该工作的事实,这个问题主要是检查我的实现是否正确并且可能获得更好的替代方案.
解决方法
不,因为inited不易变. volatile为您提供内存释放并获取您需要的围栏,以便建立正确的先发生关系.
如果在inited之前没有释放围栏设置为true,那么在另一个线程读入并且将其视为真时,该值可能不会完全写入,这可能导致返回半构造的对象.类似地,如果在第一次检查中读取之前有一个释放围栏但没有相应的获取围栏,那么该对象可能是完全构造的,但是看作真实的cpu核心还没有看到值的记忆效应写入(缓存一致性并不一定要求在其他核心上按顺序看到连续写入的影响).这将再次潜在地导致返回半构造的对象.
顺便说一句,这是已经非常详细记录的双重检查锁定模式的实例.
我建议不要使用捕获局部变量的lambda(这会使编译器生成一个隐式类来保存非易失性字段中的闭合变量),而是建议使用volatile属性值明确创建自己的类.
- private class Memoized<T>
- {
- public T value;
- public volatile bool inited;
- }
- private static Func<T> Memoize<T>(Func<T> func)
- {
- var memoized = new Memoized<T>();
- return () => {
- if (memoized.inited)
- return memoized.value;
- lock (memoized) {
- if (!memoized.inited) {
- memoized.value = func();
- memoized.inited = true;
- }
- }
- return memoized.value;
- };
- }
当然,正如其他人提到的Lazy< T>.为此目的而存在.使用它而不是自己动手,但了解事物如何运作的理论总是一个好主意.