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

时间:2013-10-24 13:53:38

标签: c# multithreading

请考虑以下代码:

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添加到列表中,导致字典尝试添加重复键?

7 个答案:

答案 0 :(得分:18)

不,它不是线程安全的。您需要锁定list.Contains,因为可以切换线程并在if测试和添加数据之间再次返回。另一个线程可能同时添加了数据。

答案 1 :(得分:11)

您需要锁定整个操作(检查并添加),否则多个线程可能会尝试添加相同的值。

Thread Timeline

我建议使用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的指南,这很好

需要牢记的一些指导原则

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

答案 5 :(得分:0)

我将假设你的意思是写ContainsKey而不是ContainsContains上的Dictionary已明确实施,因此无法通过您声明的类型访问它。 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功能。不同之处在于数据结构维护了几种可能的锁定选项,因此在大多数情况下几乎没有锁争用。这就是为什么这个数据结构被称为“低锁”或“并发”而不是“无锁”。

答案 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");
       }
    }
}