C#此方法是线程安全的吗?

C#此方法是线程安全的吗?,c#,multithreading,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,"someotherval

考虑以下代码:

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");
    }
}
字典列表=新字典();
object lockObj=新对象();
公共void MyMethod(字符串a){
如果(列表包含(a))
返回;
锁(lockObj){
列表。添加(a,“其他价值”);
}
}
假设我同时从不同的线程调用
MyMethod(“mystring”)


是否可能有多个线程(我们将其视为两个)同时输入if
(!list.Contains(a))
语句(有几个CPU周期差异),两个线程的计算结果都是
false
,一个线程进入关键区域,而另一个线程被锁定在外部,因此,第二个线程进入并在第一个线程退出后再次将“mystring”添加到列表中,导致字典试图添加重复的密钥?

不,这不是线程安全的。您需要在
列表周围加锁。它也包含
,因为在if测试和添加数据之间,一个线程可能会被调出,然后又被调回。另一个线程可能同时添加了数据。

您需要锁定整个语句。您可能会在
.Contains
部分遇到问题(您现在的代码方式)

锁定后应检查列表。e、 g

if (list.Contains(a))
return;

    lock (lockObj) {
       if (list.Contains(a))
         return;
       list.Add(a);
    }
}
private Dictionary list=new Dictionary();
公共void MyMethod(字符串a){
锁(列表){
如果(列表包含(a))
返回;
列表。添加(a,“其他价值”);
}
}
看看这本指南,很好

需要牢记的一些准则

  • 当锁定多个可写值时,通常锁定私有静态对象
  • 不要锁定作用域在类或本地方法之外的对象,例如
    lock(this)
    ,这可能会导致死锁
  • 如果对象是唯一同时访问的对象,则可以锁定正在更改的对象
  • 确保您锁定的对象不为空
  • 只能锁定引用类型

  • 您可以首先执行非锁定检查,但如果希望线程安全,则需要在锁内再次重复该检查。这样你就不会锁定,除非你必须这样做,并确保线程安全

    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");
           }
        }
    }
    
    字典列表=新字典();
    object lockObj=新对象();
    公共void MyMethod(字符串a){
    如果(列表包含(a))
    返回;
    锁(lockObj){
    如果(!list.Contains(a)){
    列表。添加(a,“其他价值”);
    }
    }
    }
    
    您需要锁定整个操作(检查并添加),否则多个线程可能会尝试添加相同的值

    我建议使用,因为它是线程安全的

    private readonly ConcurrentDictionary<string, string> _items
        = new ConcurrentDictionary<string, string>();
    
    public void MyMethod(string item, string value)
    {
        _items.AddOrUpdate(item, value, (i, v) => value);
    }
    
    专用只读ConcurrentDictionary\u项
    =新的ConcurrentDictionary();
    公共void MyMethod(字符串项、字符串值)
    {
    _添加或更新(项目,值,(i,v)=>值);
    }
    
    我假设你的意思是写
    ContainsKey
    而不是
    Contains
    <代码>包含
    字典上的
    是显式实现的,因此无法通过您声明的类型访问它。1

    你的代码不安全。原因是没有任何东西阻止同时执行
    ContainsKey
    Add
    。实际上,这会引入一些非常显著的故障场景。因为我研究了
    字典
    是如何实现的,所以我可以看到您的代码可能会导致数据结构包含重复项的情况。我的意思是它实际上包含了重复的内容。异常不一定会被抛出。其他的失败场景越来越奇怪,但我不会在这里讨论

    对代码的一个微不足道的修改可能涉及双重检查锁定模式的变化

    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的评论,我可能错过了一个事实:我们谈论的是
    词典
    ,而不是
    列表
    。事实上,基于
    包含
    调用,我首先想到的是一个
    列表


    2在
    ConcurrentDictionary
    上,读卡器完全无锁。但是,writers始终锁定(即添加和更新;删除操作仍然没有锁定)。这包括
    GetOrAdd
    功能。不同之处在于数据结构维护了几个可能的锁选项,因此在大多数情况下很少或没有锁争用。这就是为什么称此数据结构为“低锁”或“并发”而不是“无锁”的原因。

    问题在于,如果另一个线程正在修改Collectional right,microsoft不保证list.Contains方法会起作用,公平点:这似乎不是100%安全的。在计算锁定区域中的Contains()时,另一个线程可能正在写入列表。调用字典
    list
    实际上意味着
    public void MyMethod(string a) 
    {
      // The variable 'dictionary' is a ConcurrentDictionary.
      dictionary.GetOrAdd(a, "someothervalue");
    }