字典更新线程安全

本文关键字:安全 线程 更新 字典 | 更新日期: 2023-09-27 18:37:25

以下代码线程安全吗?

var dict = new Dictionary<int, string>()
    { { 0, "" }, { 1, "" }, { 2, "" }, { 3, "" } };
var nums = dict.Keys.ToList();
Parallel.ForEach(nums, num =>
            {
                dict[num] = LongTaskToGenerateString();
            });
return dict;

字典更新线程安全

不,Dictionary<TKey, TValue>类对于修改不是线程安全的,如文档所示:

只要不修改集合,Dictionary<TKey, TValue>就可以同时支持多个读取器。即便如此,通过集合进行枚举本质上也不是线程安全的过程。在极少数情况下,枚举与写入访问争用,则必须在整个枚举期间锁定集合。若要允许多个线程访问集合以进行读取和写入,必须实现自己的同步。

在您的情况下,如果某些线程几乎同时完成LongTaskToGenerateString,则其字典更新将干扰。

您可以使用 SyncRoot 属性手动同步访问,或者只采用 ConcurrentDictionary<TKey, TValue> 类,如 asawyer 在注释中建议的那样。

此实现表明,如果您只更新现有键的值,它应该没问题(也看这个) - 不优雅的效果可能是 version 属性的值不准确。它用于防止在枚举集合时修改集合,因此它最终达到哪个值并不重要。不知道对此有任何保证。

看起来

您的字典只是用作返回值,实际上从未用于查找键。在这种情况下,在计算出所有最终输出值之前,您甚至不需要字典。

因此,您可以使用 PLINQ 来实现此目的:

var nums = new[] { 0, 1, 2, 3 };
var dict = nums.AsParallel()
               .Select(num => new KeyValuePair<int, string>
                                  (num, LongTaskToGenerateString(num)));
               .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
return dict;

为了保证线程安全,请使用 ConcurrentDictionary