请考虑以下代码:
Dictionary<string, string> list = new Dictionary<string, string>();
object lockObj = new object();
public void MyMethod(string a) {
if (list.Contains(a))
return;
lock (lockObj) {
list.Add(a,"someothervalue");
}
}
假设我正在同时从不同的线程调用MyMethod("mystring")
。
是否可能有多个线程(我们只需将其作为两个)同时输入if (!list.Contains(a))
语句(具有一些CPU周期差异),两个线程都被评估为{ {1}}并且一个线程进入关键区域,而另一个线程被锁定在外部,所以第二个线程进入并在第一个线程退出后再次将false
添加到列表中,导致字典尝试添加重复键?
答案 0 :(得分:18)
不,它不是线程安全的。您需要锁定list.Contains
,因为可以切换线程并在if测试和添加数据之间再次返回。另一个线程可能同时添加了数据。
答案 1 :(得分:11)
您需要锁定整个操作(检查并添加),否则多个线程可能会尝试添加相同的值。
我建议使用ConcurrentDictionary(TKey, TValue)
,因为它的设计是线程安全的。
private readonly ConcurrentDictionary<string, string> _items
= new ConcurrentDictionary<string, string>();
public void MyMethod(string item, string value)
{
_items.AddOrUpdate(item, value, (i, v) => value);
}
答案 2 :(得分:1)
你需要锁定整个声明。您可能会遇到.Contains
部分(代码现在的方式)的问题
答案 3 :(得分:1)
锁定后应检查列表。 e.g。
if (list.Contains(a))
return;
lock (lockObj) {
if (list.Contains(a))
return;
list.Add(a);
}
}
答案 4 :(得分:1)
private Dictionary<string, string> list = new Dictionary<string, string>();
public void MyMethod(string a) {
lock (list) {
if (list.Contains(a))
return;
list.Add(a,"someothervalue");
}
}
查看locking的指南,这很好
需要牢记的一些指导原则
lock(this)
等本地方法,这可能会导致死锁!答案 5 :(得分:0)
我将假设你的意思是写ContainsKey
而不是Contains
。 Contains
上的Dictionary
已明确实施,因此无法通过您声明的类型访问它。 1
您的代码不安全。原因是因为没有任何东西阻止ContainsKey
和Add
同时执行。实际上有一些非常引人注目的故障情景会引入。因为我查看了如何实现Dictionary
,我可以看到您的代码可能导致数据结构包含重复的情况。我的意思是字面上包含重复项。不一定会抛出异常。其他失败的情况只是变得越来越陌生和陌生,但我不会进入那里。
对代码进行一项微不足道的修改可能涉及双重检查锁定模式的变化。
public void MyMethod(string a)
{
if (!dictionary.ContainsKey(a))
{
lock (dictionary)
{
if (!dictionary.ContainsKey(a))
{
dictionary.Add(a, "someothervalue");
}
}
}
}
当然,由于我已经陈述的原因,这并不安全。实际上,除了最简单的情况(如单例的规范实现)之外,双重检查的锁定模式在所有情况下都很难正确。这个主题有很多变化。您可以使用TryGetValue
或默认索引器进行尝试,但最终所有这些变体都是错误的。
那么如果不采取锁定怎么能正确地完成呢?你可以试试ConcurrentDictionary
。它具有方法GetOrAdd
,它在这些场景中非常有用。你的代码看起来像这样。
public void MyMethod(string a)
{
// The variable 'dictionary' is a ConcurrentDictionary.
dictionary.GetOrAdd(a, "someothervalue");
}
这就是它的全部。 GetOrAdd
函数将检查项目是否存在。如果没有,那么它将被添加。否则,它将保留数据结构。这一切都是以线程安全的方式完成的。在大多数情况下,ConcurrentDictionary
无需等待锁即可完成此操作。 2
1 顺便说一下,你的变量名也是令人讨厌的。如果不是Servy的评论,我可能错过了我们讨论Dictionary
而不是List
的事实。事实上,基于Contains
来电,我首先想到的是我们在讨论List
。
2 在ConcurrentDictionary
读者完全无锁。但是,编写者总是采取锁定(添加和更新;删除操作仍然是无锁的)。这包括GetOrAdd
功能。不同之处在于数据结构维护了几种可能的锁定选项,因此在大多数情况下几乎没有锁争用。这就是为什么这个数据结构被称为“低锁”或“并发”而不是“无锁”。
答案 6 :(得分:-2)
您可以先进行非锁定检查,但如果您想要是线程安全的,则需要在锁定内再次重复检查。这样你就不会锁定,除非你必须并确保线程安全。
Dictionary<string, string> list = new Dictionary<string, string>();
object lockObj = new object();
public void MyMethod(string a) {
if (list.Contains(a))
return;
lock (lockObj) {
if (!list.Contains(a)){
list.Add(a,"someothervalue");
}
}
}