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

时间:2015-04-02 15:32:54

标签: 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
  }
}

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

3 个答案:

答案 0 :(得分:5)

如果您使用ConcurrentDictionary代替Dictionary,则可获得两项重要改进:

  1. 您不需要打扰显式锁定
  2. 你有一个(保证是可观察的原子)GetOrAdd方法,它允许你从字典中获取一个值,或者如果一个人不存在则添加一个值。
  3. 结合使用Lazy<T>,它允许您创建一个对象,该对象定义其值作为昂贵计算的结果,它确保仅运行一次,仅在需要时运行,然后缓存并再次返回如果反复询问价值,也会再次。

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

    请注意,一旦我们从Lazy返回GetOrAdd,就意味着:

    1. 它是一个新的懒惰,甚至没有开始计算该值,在这种情况下,它将计算该值并将其返回。
    2. 它是一个现有的懒惰,具有可以立即返回的已计算值。
    3. 现有的懒惰,其值尚未完成计算;它等待操作完成,然后该值将返回到等待计算的所有调用。
    4. 在任何情况下都不会为同一个密钥多次调用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;
      }
      

      就.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;
      }
      

      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;
                      }
                  }
              }
          }
      }
      

答案 1 :(得分:1)

给定.NET 2.0约束的一个简单解决方案是保持Dictionary<int, object>查找该键的锁对象,并锁定该对象。因此,您的锁定更细粒度,因此支持更多的并发性。也就是说,你需要另一种类型的锁来处理你有一个新看不见的int的情况。

这样的事情:

internal class Memory
{
    public int GetData(int key)
    {
        int result;
        object locker;

        // short time lock, blocks all readers
        lock (lockObject)
            if (!m_locks.TryGetValue(key, out  locker))
            {
                locker = m_locks[key] = new object();
            }

        // long time lock, but only for readers of this key during expensive op
        lock (locker)
            if (!m_values.TryGetValue(key, out result))
            {
                result = m_values[key] = VeryExpensiveComputationMethod(key);
            }

        return result;
    }

    private readonly Object lockObject = new Object();
    private Dictionary<int, int> m_values;
    private Dictionary<int, object> m_locks;
}

答案 2 :(得分:0)

Pure .NET2.0解决方案:

public delegate T Compute<T,TParameter>(TParameter parameter);

public sealed class Lazy<T,TParameter>
{
  private T m_Result;
  private volatile bool m_IsInitialized;
  private object m_SyncRoot = new object();
  private Compute<T,TParameter> m_Compute;
  private TParameter m_Context;

  public Lazy(Compute<T,TParameter> compute, TParameter context)
  {
    if (compute == null)
    {
      throw new ArgumentNullException("compute");
    }

    m_Compute = compute;
    m_Context = context;
  }

  public T Value
  {
    get
    {
      if (!m_IsInitialized)
      {
        lock (m_SyncRoot)
        {
          if (!m_IsInitialized)
          {
            m_Result = m_Compute.Invoke(m_Context);
            m_Compute = null;
            m_Context = default(TParameter);
            m_IsInitialized = true;
          }
        }
      }
      return m_Result;
    }
  }
}

class Memory
{
  static int VeryExpensiveComputationMethod(int key)
  {
    return key;
  }

  private Dictionary<int, Lazy<int,int>> m_Values = new Dictionary<int, Lazy<int,int>>();
  private object m_SyncRoot = new object();

  public int GetData(int key)
  {
    Lazy<int,int> result;
    lock (m_SyncRoot)
    {
      if (!m_Values.TryGetValue(key, out result))
      {
        result = new Lazy<int,int>(VeryExpensiveComputationMethod, key);
        m_Values[key] = result;
      }
    }
    return result.Value;
  }
}