通过从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(...);
}我能够让它工作,但这导致了一些丑陋的代码。我的问题是,有没有更好、更优雅的方式来实现线程安全的字典?
发布于 2008-10-01 14:49:48
正如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...
}MSDN:文档指出枚举本质上不是线程安全的。这可能是在类外部公开同步对象的原因之一。另一种方法是提供一些方法,用于在所有成员上执行操作,并锁定成员的枚举。这样做的问题是,您不知道传递给该函数的操作是否调用您的字典中的某个成员(这将导致死锁)。公开synchronization对象允许使用者做出这些决定,并且不会隐藏类内部的死锁。
发布于 2010-09-14 03:11:22
支持并发的.NET 4.0类被命名为ConcurrentDictionary。
发布于 2008-12-30 00:07:59
尝试内部同步几乎肯定是不够的,因为它处于太低的抽象级别。假设您将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);
}答案很简单,不是。在某个时刻,Add方法将抛出一个异常,指示该键已经存在于字典中。你可能会问,线程安全的字典怎么可能做到这一点?因为每个操作都是线程安全的,所以两个操作的组合就不是线程安全的,因为另一个线程可以在您对ContainsKey和Add的调用之间修改它。
这意味着要正确地编写这种类型的场景,你需要一个字典之外的锁,例如
lock (mySafeDictionary)
{
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
}但现在,由于您必须编写外部锁定代码,您混淆了内部和外部同步,这总是会导致代码不清楚和死锁等问题。所以最终你可能会更好的选择:
Dictionary<TKey, TValue>并进行外部同步,将其上的复合操作包含在内,或者IDictionary<T>)编写一个新的线程安全包装器,该包装器结合了诸如AddIfNotContained方法之类的操作,因此您永远不需要组合来自它的操作。(我自己倾向于使用#1 )
https://stackoverflow.com/questions/157933
复制相似问题