首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实现线程安全字典的最佳方式是什么?

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

Stack Overflow用户
提问于 2008-10-01 14:36:52
回答 8查看 67.8K关注 0票数 110

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

代码语言:javascript
复制
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对象:

示例:

代码语言:javascript
复制
lock (m_MySharedDictionary.SyncRoot)
{
    m_MySharedDictionary.Add(...);
}

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

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2008-10-01 14:49:48

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

代码语言:javascript
复制
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...
}

MSDN:文档指出枚举本质上不是线程安全的。这可能是在类外部公开同步对象的原因之一。另一种方法是提供一些方法,用于在所有成员上执行操作,并锁定成员的枚举。这样做的问题是,您不知道传递给该函数的操作是否调用您的字典中的某个成员(这将导致死锁)。公开synchronization对象允许使用者做出这些决定,并且不会隐藏类内部的死锁。

票数 43
EN

Stack Overflow用户

发布于 2010-09-14 03:11:22

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

票数 207
EN

Stack Overflow用户

发布于 2008-12-30 00:07:59

尝试内部同步几乎肯定是不够的,因为它处于太低的抽象级别。假设您将AddContainsKey操作分别设置为线程安全,如下所示:

代码语言:javascript
复制
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);
    }
}

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

代码语言:javascript
复制
if (!mySafeDictionary.ContainsKey(someKey))
{
    mySafeDictionary.Add(someKey, someValue);
}

答案很简单,不是。在某个时刻,Add方法将抛出一个异常,指示该键已经存在于字典中。你可能会问,线程安全的字典怎么可能做到这一点?因为每个操作都是线程安全的,所以两个操作的组合就不是线程安全的,因为另一个线程可以在您对ContainsKeyAdd的调用之间修改它。

这意味着要正确地编写这种类型的场景,你需要一个字典之外的锁,例如

代码语言:javascript
复制
lock (mySafeDictionary)
{
    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
}

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

  1. 使用普通的Dictionary<TKey, TValue>并进行外部同步,将其上的复合操作包含在内,或者
  2. 使用不同的接口(即不是IDictionary<T>)编写一个新的线程安全包装器,该包装器结合了诸如AddIfNotContained方法之类的操作,因此您永远不需要组合来自它的操作。

(我自己倾向于使用#1 )

票数 63
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/157933

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档