繁体   English   中英

C#这个方法线程安全吗?

[英]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");
    }
}

看看这个锁定指南,这很好

要牢记一些准则

  1. 通常在锁定多个可写值时锁定私有静态对象
  2. 不要锁定具有类外的范围或本地方法(如lock(this) ,这可能导致死锁!
  3. 如果它是唯一同时访问的对象,则可以锁定要更改的对象
  4. 确保锁定的对象不为空!
  5. 您只能锁定引用类型

我假设你的意思是写ContainsKey而不是Contains 显式实现了Dictionary上的Contains ,因此无法通过您声明的类型访问它。 1

你的代码不安全。 原因是因为没有什么能阻止ContainsKeyAdd执行。 实际上有一些非常引人注目的故障情景会引入。 因为我看了如何实现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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM