Threadsafe懒惰类

时间:2009-12-02 16:05:18

标签: c# multithreading thread-safety

我有类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

显然 在我的测试中起作用,但我无法知道它是否会在生产中爆炸。

我的班级线程安全吗?如果没有,可以采取哪些措施来保证线程安全?

4 个答案:

答案 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)

您的代码存在一些问题:

  1. 您需要一个对象来执行锁定。不要锁定变化的变量 - 锁总是处理对象,所以如果更改了getValue,多个线程可能会立即进入锁定部分。

  2. 如果多个线程正在等待锁定,那么所有线程都将在彼此之后评估函数f()。您必须在锁内部检查该功能尚未评估。

  3. 即使修复了上述问题,您也可能需要一个内存屏障,以确保只有在新值存储到内存后代理才会被替换。

  4. 但是,我会使用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();
    }
}