在线程之间在字典中共享数据

Hon*_*vak 2 c# multithreading dictionary

假设代码:

class Memory {
  Dictionary<int, int> m_values;
  Object lockObject = new Object();

  public int GetData(int key) {
    int result;
    lock (lockObject) {
      if (!m_values.TryGetValue(key, out result)) {
        result = VeryExpensiveComputationMethod(key);
        m_values[key] = result;
      }
    }
    return result
  }
}
Run Code Online (Sandbox Code Playgroud)

这是安全的,但问题是它不是很有效.有些人想过如何使用与.NET 2.0兼容的代码更好地做到这一点?(在最佳方案中,只有等待相同键的相同结果的线程才应该等待)

Ser*_*rvy 5

如果您使用ConcurrentDictionary而不是a,Dictionary则会获得两项关键改进:

  1. 您不需要打扰显式锁定
  2. 您有一个(保证是可观察的原子)GetOrAdd方法,它允许您从字典中获取值或添加值(如果不存在).

将它与使用相结合Lazy<T>,它允许您创建一个对象,该对象定义其值作为昂贵计算的结果,它确保仅运行一次,仅在需要时运行,然后缓存并反复返回,如果反复询问该值.

我们现在可以创建一个ConcurrentDictionary<int, Lazy<int>>,在这两种类型之间,它基本上为我们完成了所有工作:

请注意,一旦我们LazyGetOrAdd它返回,意味着:

  1. 它是一个新的懒惰,甚至没有开始计算值,在这种情况下,它将计算值并返回它.
  2. 它是一个现有的懒惰,具有可以立即返回的已计算值.
  3. 它是一个现有的懒惰,其值尚未完成计算; 它将等待操作完成,然后该值将返回到等待其计算的所有调用.

在任何情况下都VeryExpensiveComputationMethod不会为同一个密钥多次调用.

private ConcurrentDictionary<int, Lazy<int>> values =
    new ConcurrentDictionary<int, Lazy<int>>();

public int GetData(int key)
{
    //Note that this doesn't actually run VeryExpensiveComputationMethod 
    //until .Value is called on it
    var lazy = new Lazy<int>(() => VeryExpensiveComputationMethod(key));
    return values.GetOrAdd(key, lazy).Value;
}
Run Code Online (Sandbox Code Playgroud)

就.NET 2.0解决方案而言,您没有任何一种类型.使用Dictionary和锁定当然是可行的,只是不太干净:

private Dictionary<int, Lazy<int>> values;
private object sync = new object();
public int GetData(int key)
{
    Lazy<int> lazy;
    lock (sync)
    {
        if (!values.TryGetValue(key, out lazy))
        {
            lazy = new Lazy<int>(delegate
            {
                return VeryExpensiveComputationMethod(key);
            });
            values.Add(key, lazy);
        }
    }
    return lazy.Value;
}
Run Code Online (Sandbox Code Playgroud)

至于Lazy,您只需创建自己的版本:

public delegate T Func<T>();
public class Lazy<T>
{
    private object key = new object();
    private Func<T> generator;
    private T value;
    public Lazy(Func<T> generator)
    {
        this.generator = generator;
    }

    private volatile bool hasComputedValue;
    public bool HasComputedValue { get { return hasComputedValue; } }

    public T Value
    {
        get
        {
            lock (key)
            {
                if (HasComputedValue)
                    return value;
                else
                {
                    value = generator();
                    hasComputedValue = true;
                    generator = null;
                    return value;
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)