[英]C# Is this method thread safe?
请考虑以下代码:
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周期差异),两个线程都被评估为false
和一个线程进入关键区域,而另一个线程被锁定在外面,所以第二个线程进入并在第一个线程退出后再次向列表添加"mystring"
,导致字典尝试添加重复键?
不,它不是线程安全的。 你需要锁定list.Contains
,因为可以在if测试和添加数据之间切换一个线程并再次返回。 另一个线程可能同时添加了数据。
您需要锁定整个操作(检查并添加),否则多个线程可能会尝试添加相同的值。
我建议使用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);
}
你需要锁定整个语句。 您可能会遇到.Contains
部分的问题(您的代码现在的方式)
您应该在锁定后检查列表。 例如
if (list.Contains(a))
return;
lock (lockObj) {
if (list.Contains(a))
return;
list.Add(a);
}
}
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");
}
}
看看这个锁定指南,这很好
要牢记一些准则
lock(this)
,这可能导致死锁! 我假设你的意思是写ContainsKey
而不是Contains
。 显式实现了Dictionary
上的Contains
,因此无法通过您声明的类型访问它。 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
函数。 不同之处在于数据结构维护了几种可能的锁定选项,因此在大多数情况下几乎没有锁争用。 这就是为什么这个数据结构被称为“低锁”或“并发”而不是“无锁”。
您可以先进行非锁定检查,但如果您想要是线程安全的,则需要在锁定内再次重复检查。 这样您就不会锁定,除非您必须并确保线程安全。
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");
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.