C# 什么';实现线程安全字典的最佳方法是什么?
通过从IDictionary派生并定义私有SyncRoot对象,我能够在C#中实现线程安全字典:C# 什么';实现线程安全字典的最佳方法是什么?,c#,.net,thread-safety,collections,C#,.net,Thread Safety,Collections,通过从IDictionary派生并定义私有SyncRoot对象,我能够在C#中实现线程安全字典: public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue> { private readonly object syncRoot = new object(); private Dictionary<TKey, TValue> d = new Dictionary<
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属性。您在字典的方法中拥有的锁就足够了 详细说明: 最终发生的情况是,您的词典被锁定的时间比需要的时间更长 在您的情况下会发生以下情况: 假设线程A在调用m_mySharedDictionary.Add之前获得了SyncRoot上的锁。线程B随后尝试获取锁,但被阻止。事实上,所有其他线程都被阻塞了。允许线程A调用Add方法。在Add方法中的lock语句中,允许线程A再次获取锁,因为它已经拥有它。在退出方法内部和方法外部的锁上下文时,线程A已释放所有锁,允许其他线程继续
您可以简单地允许任何使用者调用Add方法,因为SharedDictionary类Add方法中的lock语句将具有相同的效果。此时,您有冗余锁定。如果必须对dictionary对象执行两个需要保证连续发生的操作,则只能在其中一个dictionary方法之外锁定SyncRoot。正如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...
}
公共类安全字典:IDictionary
{
私有只读对象syncRoot=新对象();
私有字典d=新字典();
公共无效添加(TKey键,TValue值)
{
锁定(同步根)
{
d、 添加(键、值);
}
OnItemed(eventags.Empty);
}
添加了公共事件事件处理程序项;
受保护的虚拟服务器(事件参数e)
{
EventHandler=ItemAdded;
if(处理程序!=null)
处理者(本,e);
}
//更多IDictionary成员。。。
}
编辑:MSDN文档指出枚举本质上不是线程安全的。这可能是在类外部公开同步对象的原因之一。另一种方法是提供一些对所有成员执行操作的方法,并锁定成员的枚举。问题是,您不知道传递给该函数的操作是否调用字典的某个成员(这将导致死锁)。公开同步对象允许使用者做出这些决定,并且不会在类中隐藏死锁。您不应该通过属性发布私有锁对象。锁定对象应单独存在,以用作集合点
如果使用标准锁时性能不佳,那么Wintellect的锁集合将非常有用。您描述的实现方法存在几个问题
就我个人而言,我发现实现线程安全类的最佳方法是通过不变性。它确实减少了线程安全性可能遇到的问题数量。查看更多详细信息 尝试内部同步几乎肯定是不够的,因为它的抽象级别太低了。假设您将
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);
}
}
但是现在,由于您必须编写外部锁定代码,您将内部和外部同步混淆,这总是导致代码不清晰和死锁等问题。因此,最终您可能会更好地选择:
字典
并在外部同步,包括其上的复合操作,或IDictionary
)组合操作,例如AddifyNotContained
方法,这样您就不需要组合来自它的操作(我倾向于自己去)支持并发的.NET 4.0类被命名。只是想一想,为什么不重新创建字典呢?如果读取是大量写入,那么锁定将同步所有请求 范例
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);
}
private static readonly object Lock=new object();
私有静态字典_dict=new Dictionary();
私有字符串获取(字符串密钥)
{
锁(锁)
{
字符串返回值;
if(_dict.TryGetValue(键,输出返回值))
返回值;
returnValue=“查找新的va
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);
}