我有类Lazy
懒惰地评估表达式:
public sealed class Lazy<T>
{
Func<T> getValue;
T value;
public Lazy(Func<T> f)
{
getValue = () =>
{
lock (getValue)
{
value = f();
getValue = () => value;
}
return value;
};
}
public T Force()
{
return getValue();
}
}
基本上,我试图避免在评估对象后锁定对象的开销,所以我在调用时用另一个函数替换getValue
。
显然 在我的测试中起作用,但我无法知道它是否会在生产中爆炸。
我的班级线程安全吗?如果没有,可以采取哪些措施来保证线程安全?
答案 0 :(得分:2)
难道你不能通过使用标志或保护值来完全重新评估函数吗?即:
public sealed class Lazy<T>
{
Func<T> f;
T value;
volatile bool computed = false;
void GetValue() { lock(LockObject) { value = f(); computed = true; } }
public Lazy(Func<T> f)
{
this.f = f;
}
public T Force()
{
if (!computed) GetValue();
return value;
}
}
答案 1 :(得分:2)
您的代码存在一些问题:
您需要一个对象来执行锁定。不要锁定变化的变量 - 锁总是处理对象,所以如果更改了getValue,多个线程可能会立即进入锁定部分。
如果多个线程正在等待锁定,那么所有线程都将在彼此之后评估函数f()。您必须在锁内部检查该功能尚未评估。
即使修复了上述问题,您也可能需要一个内存屏障,以确保只有在新值存储到内存后代理才会被替换。
但是,我会使用Konrad Rudolph的标志方法(只是确保你不要忘记为此所需的“易变性”)。这样,无论何时检索到值,您都不需要调用委托(委托调用非常快;但不是它们不像简单地检查bool那么快)。
答案 2 :(得分:0)
我不完全确定你要对这段代码做什么,但我just published an article on The Code Project构建了一个“懒惰”类,它自动异步调用一个worker函数并存储它的值。 / p>
答案 3 :(得分:0)
这看起来更像是一种缓存机制,而不是“懒惰评估”。此外,请勿更改lock
块中锁定引用的值。使用临时变量锁定。
你现在的等待会在很多情况下起作用,但如果你有两个不同的线程,试着按照这个顺序评估表达式:
Thread 1
Thread 2
Thread 1 completes
线程2永远不会完成,因为线程1将释放与用于获取锁的不同引用上的锁(更准确地说,他将释放不存在的锁,因为新创建的引用从未被锁定开头),并且不释放原始锁定,即阻止线程2。
虽然我不完全确定这会做什么(除了执行结果的表达式和缓存的同步评估),这应该使它更安全:
public sealed class Lazy<T>
{
Func<T> getValue;
T value;
object lockValue = new object();
public Lazy(Func<T> f)
{
getValue = () =>
{
lock (lockValue)
{
value = f();
getValue = () => value;
}
return value;
};
}
public T Force()
{
return getValue();
}
}