线程安全与字典< INT,INT>在.net [英] Thread safety with Dictionary<int,int> in .Net

查看:175
本文介绍了线程安全与字典< INT,INT>在.net的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这样的功能:

 静态字典< INT,INT> KeyValueDictionary =新字典< INT,INT>();
静态无效IncreaseValue(INT KEYID,INT调整)
{
    如果(!KeyValueDictionary.ContainsKey(KEYID))
    {
        KeyValueDictionary.Add(KEYID,0);
    }
    KeyValueDictionary [KEYID] + =调整;
}
 

这我还以为不会是线程安全的。然而,迄今为止在测试它我还没有从多个线程同时调用它时到任何异常。

我的问题:它是线程安全的或有我只是很幸运这么远吗?如果是线程安全的,为什么?

解决方案
  

不过,到目前为止,在测试它从多个线程同时调用它时,我还没有看到任何异常。

     

它是线程安全的或有我只是很幸运这么远吗?如果是线程安全的,为什么?

你得到幸运。这些类型的错误与线程是这样容易使因为测试你可以给你,你做了正确的事安全的错觉。

原来,词典< TKEY的,TValue> 不是线程安全的,当你有多个作家。该文件明确规定:

  

A 词典< TKEY的,TValue> 可以同时支持多个阅读,只要集合不会被改动。即便如此,通过集合进行枚举本质上并不是一个线程安全的过程。在枚举抗衡写访问罕见的情况下,集合必须在整个枚举过程中被锁定。 要允许多个线程读取和写入访问的集合,您必须实现自己的同步。

另外,使用 ConcurrentDictionary 。但是,你仍然必须编写正确的code(请参阅下面的注释)。

在除了缺乏线程安全与词典< TKEY的,TValue> ,你很幸运,以避免您的code是危险的缺陷。这里是你如何能得到一个错误与您的code:

 静态无效IncreaseValue(INT KEYID,INT调整){
    如果(!KeyValueDictionary.ContainsKey(KEYID)){
        // 一个
        KeyValueDictionary.Add(KEYID,0);
    }
    KeyValueDictionary [KEYID] + =调整;
}
 

  1. 词典是空的。
  2. 在线程1进入方法的keyid = 17 。由于词典是空的,有条件的如果返回和线程1达到code线标记 A
  3. 在线程1暂停和线程2进入方法的keyid = 17 。由于词典是空的,有条件的如果返回和线程2达到code线标记 A
  4. 在线程2被暂停和线程1简历。现在线程1增加了(17,0)到字典中。
  5. 在线程1被暂停,现在线程2简历。现在,线程2试图(17,0)添加到字典中。因为一个键冲突,则抛出异常。

有在其中一个例外可能发生其他方案。例如,线程1可以暂停加载时的值 KeyValueDictionary [KEYID] (说它加载的keyid = 17 ,并获得价值 42 ),线程2可以派上并修改值(比如它加载的keyid = 17 ,增加了调整 27 ),现在线程1简历,并增加了其调整到它装载的价值(特别是它不会看到线程2所提出的修改与的keyid = 17相关的值!)。

注意,即使使用 ConcurrentDictionary< TKEY的,TValue> 可能会导致上述错误!您的code不是安全不相关的线程安全或缺乏的原因,其用于词典< TKEY的,TValue>

为了让您的code是线程安全的并发字典,你不得不说:

  KeyValueDictionary.AddOrUpdate(KEYID,调整,(键,值)=>价值+调整);
 

下面我们使用<一个href="http://msdn.microsoft.com/en-us/library/ee378665.aspx"><$c$c>ConcurrentDictionary.AddOrUpdate.

I have this function:

static Dictionary<int, int> KeyValueDictionary = new Dictionary<int, int>();
static void IncreaseValue(int keyId, int adjustment)
{
    if (!KeyValueDictionary.ContainsKey(keyId))
    {
        KeyValueDictionary.Add(keyId, 0);
    }
    KeyValueDictionary[keyId] += adjustment;
}

Which I would have thought would not be thread safe. However, so far in testing it I have not seen any exceptions when calling it from multiple threads at the same time.

My questions: Is it thread safe or have I just been lucky so far? If it is thread safe then why?

解决方案

However, so far in testing it I have not seen any exceptions when calling it from multiple threads at the same time.

Is it thread safe or have I just been lucky so far? If it is thread safe then why?

You're getting lucky. These types of bugs with threads are so easy to make because testing can you give you a false sense of security that you did things correctly.

It turns out that Dictionary<TKey, TValue> is not thread-safe when you have multiple writers. The documentation explicitly states:

A Dictionary<TKey, TValue> can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Alternatively, use ConcurrentDictionary. However, you still must write correct code (see note below).

In addition to the lack of thread-safety with Dictionary<TKey, TValue> which you've been lucky to avoid, your code is dangerously flawed. Here's how you can get a bug with your code:

static void IncreaseValue(int keyId, int adjustment) {
    if (!KeyValueDictionary.ContainsKey(keyId)) {
        // A
        KeyValueDictionary.Add(keyId, 0);
    }
    KeyValueDictionary[keyId] += adjustment;
}

  1. Dictionary is empty.
  2. Thread 1 enters the method with keyId = 17. As the Dictionary is empty, the conditional in the if returns true and thread 1 reaches the line of code marked A.
  3. Thread 1 is paused and thread 2 enters the method with keyId = 17. As the Dictionary is empty, the conditional in the if returns true and thread 2 reaches the line of code marked A.
  4. Thread 2 is paused and thread 1 resumes. Now thread 1 adds (17, 0) to the dictionary.
  5. Thread 1 is paused and now thread 2 resumes. Now thread 2 tries to add (17, 0) to the dictionary. An exception is thrown because of a key violation.

There are other scenarios in which an exception can occur. For example, thread 1 could be paused while loading the value of KeyValueDictionary[keyId] (say it loads keyId = 17, and obtains the value 42), thread 2 could come in and modify the value (say it loads keyId = 17, adds the adjustment 27), and now thread 1 resumes and adds its adjustment to the value it loaded (in particular, it doesn't see the modification that thread 2 made to the value associated with keyId = 17!).

Note that even using a ConcurrentDictionary<TKey, TValue> could lead to the above bugs! Your code is NOT safe for reasons not related to the thread-safety or lack thereof for Dictionary<TKey, TValue>.

To get your code to be thread-safe with a concurrent dictionary, you'll have to say:

KeyValueDictionary.AddOrUpdate(keyId, adjustment, (key, value) => value + adjustment);

Here we are using ConcurrentDictionary.AddOrUpdate.

这篇关于线程安全与字典&LT; INT,INT&GT;在.net的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆