代码之家  ›  专栏  ›  技术社区  ›  GP.

实现线程安全字典的最佳方法是什么?

  •  105
  • GP.  · 技术社区  · 17 年前

    通过从IDictionary派生并定义私有SyncRoot对象,我能够在C#中实现线程安全字典:

    public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
    {
        private readonly object syncRoot = new object();
        private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
    
        public object SyncRoot
        {
            get { return syncRoot; }
        } 
    
        public void Add(TKey key, TValue value)
        {
            lock (syncRoot)
            {
                d.Add(key, value);
            }
        }
    
        // more IDictionary members...
    }
    

    然后,我在整个使用者(多线程)中锁定此SyncRoot对象:

    lock (m_MySharedDictionary.SyncRoot)
    {
        m_MySharedDictionary.Add(...);
    }
    

    我能够使它工作,但这导致了一些丑陋的代码。我的问题是,有没有更好、更优雅的方法来实现线程安全字典?

    8 回复  |  直到 13 年前
        1
  •  207
  •   Uwe Keim    10 年前

    支持并发的.NET 4.0类命名为 ConcurrentDictionary .

        2
  •  63
  •   Greg Beech    17 年前

    尝试内部同步几乎肯定是不够的,因为它的抽象级别太低。假设你做了 Add ContainsKey 操作单独线程安全,如下所示:

    public void Add(TKey key, TValue value)
    {
        lock (this.syncRoot)
        {
            this.innerDictionary.Add(key, value);
        }
    }
    
    public bool ContainsKey(TKey key)
    {
        lock (this.syncRoot)
        {
            return this.innerDictionary.ContainsKey(key);
        }
    }
    

    那么,当您从多个线程调用这段假定是线程安全的代码时,会发生什么呢?它会一直正常工作吗?

    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
    

    简单的答案是否定的。在某一点上 添加 康纳斯基 添加 .

    外部 字典,例如。

    lock (mySafeDictionary)
    {
        if (!mySafeDictionary.ContainsKey(someKey))
        {
            mySafeDictionary.Add(someKey, someValue);
        }
    }
    

    但是现在,由于您必须编写外部锁定代码,您将内部和外部同步混淆,这总是导致代码不清晰和死锁等问题。因此,最终您可能会更好地选择:

    1. Dictionary<TKey, TValue> 和外部同步,包括对其进行的复合操作,或

    2. 使用不同的接口编写一个新的线程安全包装器(即 IDictionary<T> )它结合了诸如 AddIfNotContained 方法,因此您永远不需要从中组合操作。

    (我倾向于自己去)

        3
  •  43
  •   fryguybob    17 年前

    正如Peter所说,您可以在类中封装所有线程安全性。您需要小心处理您公开或添加的任何事件,确保在任何锁之外调用它们。

    public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
    {
        private readonly object syncRoot = new object();
        private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
    
        public void Add(TKey key, TValue value)
        {
            lock (syncRoot)
            {
                d.Add(key, value);
            }
            OnItemAdded(EventArgs.Empty);
        }
    
        public event EventHandler ItemAdded;
    
        protected virtual void OnItemAdded(EventArgs e)
        {
            EventHandler handler = ItemAdded;
            if (handler != null)
                handler(this, e);
        }
    
        // more IDictionary members...
    }
    

    编辑:

        4
  •  6
  •   Jonathan Webb    17 年前

    您不应该通过属性发布私有锁对象。锁定对象应单独存在,以用作集合点。

    如果使用标准锁时性能不佳,则Wintellect的 Power Threading 锁的集合可能非常有用。

        5
  •  5
  •   E. van Putten    6 年前

    1. 您不应该公开同步对象。这样做会让你向消费者敞开心扉,消费者抓住物品并锁定它,然后你就完蛋了。
    2. 您正在使用线程安全类实现一个非线程安全接口。我想这会让你在路上付出什么代价

    就我个人而言,我发现实现线程安全类的最佳方法是通过不变性。它确实减少了线程安全性可能遇到的问题数量。退房 Eric Lippert's Blog 更多细节。

        6
  •  3
  •   El Ronnoco    14 年前

    您不需要在使用者对象中锁定SyncRoot属性。您在字典的方法中拥有的锁就足够了。

    最终发生的情况是,您的词典被锁定的时间比需要的时间更长。

    在您的情况下会发生以下情况:

    假设线程A获得SyncRoot上的锁 之前

    您可以简单地允许任何使用者调用Add方法,因为SharedDictionary类Add方法中的lock语句将具有相同的效果。此时,您有冗余锁定。如果必须对dictionary对象执行两个需要保证连续发生的操作,则只能在dictionary方法之一之外锁定SyncRoot。

        7
  •  0
  •   verbedr    13 年前

    实例

        private static readonly object Lock = new object();
        private static Dictionary<string, string> _dict = new Dictionary<string, string>();
    
        private string Fetch(string key)
        {
            lock (Lock)
            {
                string returnValue;
                if (_dict.TryGetValue(key, out returnValue))
                    return returnValue;
    
                returnValue = "find the new value";
                _dict = new Dictionary<string, string>(_dict) { { key, returnValue } };
    
                return returnValue;
            }
        }
    
        public string GetValue(key)
        {
            string returnValue;
    
            return _dict.TryGetValue(key, out returnValue)? returnValue : Fetch(key);
        }
    
        8
  •  -6
  •   MagicKat    17 年前