Dictionary的线程安全在.Net中

发布于 2024-12-02 06:51:19 字数 442 浏览 0 评论 0原文

我有这个函数:

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;
}

我认为这不是线程安全的。然而,到目前为止,在测试它时,我还没有看到同时从多个线程调用它时出现任何异常。

我的问题:它是线程安全的还是到目前为止我只是幸运?如果它是线程安全的那为什么呢?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

香橙ぽ 2024-12-09 06:51:19

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

它是线程安全的还是到目前为止我只是运气好?如果它是线程安全的那为什么?

你很幸运。这些类型的线程错误很容易出现,因为测试会给你一种错误的安全感,让你觉得你做的事情是正确的。

事实证明,当您有多个编写器时, Dictionary 不是线程安全的。文档明确指出:

一个Dictionary可以同时支持多个读取器,只要集合不被修改。即便如此,通过集合进行枚举本质上并不是线程安全的过程。在枚举与写访问发生冲突的极少数情况下,必须在整个枚举期间锁定集合。 要允许多个线程访问集合以进行读写,您必须实现自己的同步。

或者,使用 ConcurrentDictionary。但是,您仍然必须编写正确的代码(请参见下面的注释)。

除了您幸运地避免的 Dictionary 缺乏线程安全性之外,您的代码还存在危险的缺陷。以下是您的代码出现错误的方法:

static void IncreaseValue(int keyId, int adjustment) {
    if (!KeyValueDictionary.ContainsKey(keyId)) {
        // A
        KeyValueDictionary.Add(keyId, 0);
    }
    KeyValueDictionary[keyId] += adjustment;
}
  1. 字典为空。
  2. 线程 1 进入带有 keyId = 17 的方法。由于字典为空,if 中的条件返回 true,线程 1 到达标记为 A 的代码行。
  3. 线程 1 暂停,线程 2 进入带有 keyId = 17 的方法。由于字典为空,if 中的条件返回 true,线程 2 到达标记为 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 也可能导致上述错误!由于与 Dictionary的线程安全无关或缺乏线程安全无关的原因,您的代码不安全。

要使您的代码使用并发字典实现线程安全,您必须说:

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

这里我们使用 ConcurrentDictionary.AddOrUpdate

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.

泛泛之交 2024-12-09 06:51:19

它不是线程安全的,但不检查,因此可能不会注意到静默损坏。

它在很长一段时间内看起来都是线程安全的,因为只有当它需要 rehash() 时,它才有可能出现异常。否则,它只会损坏数据。

It's not thread safe, but does not check and so probably doesn't notice silent corruption.

It will appear to be thread safe for a long time because only when it needs to rehash() does it have even a chance of exception. Otherwise, it just corrupts data.

趁微风不噪 2024-12-09 06:51:19

.NET 库有一个线程安全字典,即 ConcurrentDictionary http://msdn.microsoft.com/en-us/library/dd287191.aspx

更新:我没有完全回答这个问题,所以这里更新了对所提出的确切问题的更多答案。
根据 MSDN:http://msdn.microsoft.com/en-us/库/xfhwa508.aspx

字典可以同时支持多个读者,
只要集合不被修改。即便如此,列举
通过集合本质上不是线程安全的过程。在
枚举与写访问冲突的罕见情况,
整个枚举过程中必须锁定集合。为了允许
由多个线程访问以进行读写的集合,
您必须实现自己的同步。

有关线程安全的替代方案,请参阅 ConcurrentDictionary。

此类型的公共静态(在 Visual Basic 中共享)成员是线程
安全。

The .NET library has a thread safe dictionary, the ConcurrentDictionary<TKey, TValue> http://msdn.microsoft.com/en-us/library/dd287191.aspx

Updated: I didn't exactly answer the question, so here's updated with more answery to exact question posed.
As per the MSDN:http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

A Dictionary 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.

For a thread-safe alternative, see ConcurrentDictionary.

Public static (Shared in Visual Basic) members of this type are thread
safe.

抹茶夏天i‖ 2024-12-09 06:51:19

到目前为止你很幸运。它不是线程安全的。

来自Dictionary 文档...

一个字典可以支持多个阅读器
并发,只要集合不被修改。即便如此,
通过集合进行枚举本质上不是线程安全的
程序。在极少数情况下,枚举与写入发生冲突
访问时,必须在整个枚举过程中锁定集合。
允许多个线程访问集合进行读取
和写作,你必须实现自己的同步。

You've just been lucky so far. It's not thread-safe.

From the Dictionary<K,V> documentation...

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文