C# 字典中离散键并发写入的线程安全<&燃气轮机;索引器

C# 字典中离散键并发写入的线程安全<&燃气轮机;索引器,c#,.net,multithreading,dictionary,concurrency,C#,.net,Multithreading,Dictionary,Concurrency,假设您有以下代码 var dictionary = new Dictionary<int, object>(capacity: 2500); var uniqueKeys = Enumerable.Range(0, 1000).ToArray(); Parallel.ForEach(uniqueKeys, key => dictionary[key] = new object()); var字典=新字典(容量:2500); var uniqueKeys=Enumerabl

假设您有以下代码

var dictionary = new Dictionary<int, object>(capacity: 2500);

var uniqueKeys = Enumerable.Range(0, 1000).ToArray();

Parallel.ForEach(uniqueKeys, key => dictionary[key] = new object());
var字典=新字典(容量:2500);
var uniqueKeys=Enumerable.Range(0,1000).ToArray();
Parallel.ForEach(uniqueKeys,key=>dictionary[key]=newobject());
请注意,所有键都是唯一的,字典容量远远超过键的数量

问题:是否存在导致此代码不成功的任何条件

考虑到当前的情况,并且没有假设未来的假设性内部变化,您是否可以提供任何不安全访问的证据



备注:这不是or的副本,我不需要任何人告诉我,等等。

首先,我想指出的是,我在最近的项目中遇到了类似的情况,我们有一个带有键a
DateTimes
(unique)的字典,并行处理,在初始化它之后,我们有时会遇到
KeyNotFoundException
问题,但我们没有像您一样预先分配内存。可能是问题解决了吗?让我们谈谈您链接的代码

我的多线程编程老师每次遇到关于并发性的问题都会告诉我们同样的事情:

如果此时此刻数十亿个线程就在这里呢

那么,让我们看看
字典中是否存在可能的问题

dictionary[key]=newobject()

set {
    Insert(key, value, false);
}
Insert
是主要的添加方法,在
字典
类的许多地方调用。当您声明对象是唯一的时,我假设这里不会有散列冲突,也不会重写第一个方法循环中的值,所以让我们看看代码的其余部分:

int index;
if (freeCount > 0) {
    index = freeList;
    freeList = entries[index].next;
    freeCount--;
}
else {
    if (count == entries.Length)
    {
        Resize();
        targetBucket = hashCode % buckets.Length;
    }
    index = count;
    count++;
}
当您使用容量
2500
初始化字典时,在这种情况下似乎根本不调用
else
子句,因此让我们检查
if
部分:

1.如果(自由计数>0){
2//原子分配
3.索引=自由列表;
4//一些计算和原子赋值
5.自由列表=条目[索引]。下一步;
6//非线程安全操作
7.自由计数--;
8.}

这里似乎存在多个多线程问题:

  • 自由列表
    自由计数
    字段不是易变的
  • ,因此对其进行读/写很容易出错
  • 如果此时此刻数十亿个线程就在这里呢?:)
    3。索引=自由列表
    十亿个线程将获得相同的索引,因为
    自由列表
    字段的读写之间没有同步!在这之后,它们将使用竞争条件覆盖彼此的值:

    entries[index].hashCode = hashCode;
    entries[index].next = buckets[targetBucket];
    entries[index].key = key;
    entries[index].value = value;
    buckets[targetBucket] = index;
    version++;
    
  • 减量
    操作(来自@EricLippert的非常有趣的答案),因此我们将拥有字典的潜在损坏状态
  • 如果此时此刻数十亿个线程就在这里呢?:)
    5。自由列表=条目[索引]。下一步
    一些线程将
    自由列表
    值放入
    索引
    变量中,比如我们在那里有
    5
    (条目中包含链接列表,标题等于
    -1
    ),并在覆盖
    自由列表
    之前变为非活动状态。数十亿个线程一个接一个地推进链表位置,现在我们的第一个线程变为活动状态。他高兴地用过时的数据覆盖了
    自由列表
    ,在链接列表中创建了一个重影,那里的数据可以被覆盖

  • 因此,在代码执行过程中可能会发生很多问题,我个人不建议在这种情况下使用
    字典
    类。

    相反,你应该使用
    uniqueKeys.AsParallel().ToDictionary(key=>key,key=>newobject())
    @Aron我的现实世界场景与之相当,在索引数据之前先合并数据。我想找出一个我不能跳过这一步直接查字典的原因。[数以百万计的对象]这就是为什么字段应该加下划线前缀的一个重要原因,我没有考虑自由列表是一个与局部变量相对的字段。自由计数撕裂会有一个不幸的副作用,那就是不能信任可以容忍的dict.Count。然而,自由列表重复使用索引的风险才是真正的问题,因为这会导致数据丢失。@chrismaric是的,这让我在审查时心烦意乱。