我正在和TPL一起玩,并尝试通过并行阅读和写作到同一个字典,找出我能做出多大的麻烦.
所以我有这个代码:
private static void HowCouldARegularDicionaryDeadLock() { for (var i = 0; i < 20000; i++) { TryToReproduceProblem(); } } private static void TryToReproduceProblem() { try { var dictionary = new Dictionary<int,int>(); Enumerable.Range(0,1000000) .ToList() .AsParallel() .ForAll(n => { if (!dictionary.ContainsKey(n)) { dictionary[n] = n; //write } var readValue = dictionary[n]; //read }); } catch (AggregateException e) { e.Flatten() .InnerExceptions.ToList() .ForEach(i => Console.WriteLine(i.Message)); } }
这真的很混乱,有很多异常抛出,主要是关键不存在,有几个关于索引的数组的界限.
但运行该应用程序一段时间后,它挂起,cpu百分比保持在25%,机器有8个内核.
所以我假设有2个线程以满容量运行.
然后我点了点跟踪,得到这个:
它匹配我的猜测,两个线程运行在100%.
两者都运行Dictionary的FindEntry方法.
然后我再次运行应用程序,用dottrace,这个时候结果略有不同:
这一次,一个线程正在运行FindEntry,另一个插入.
我的第一个直觉是它被锁死了,但后来我以为这不可能,只有一个共享资源,而且它没有被锁定.
那么应该如何解释呢?
ps:我不是想解决这个问题,它可以通过使用ConcurrentDictionary或者并行聚合来修复.我只是在寻找一个合理的解释.
解决方法
看起来像一个竞争条件(不是一个僵局) – 正如你所说,这导致内部混乱的状态.
字典不是线程安全的,因此从单独的线程(即使只有少数几个)并发读取和写入同一容器是不安全的.
一旦比赛条件受到打击,将会发生什么变化,在这种情况下,似乎是某种无限循环.
通常,一旦需要写入访问,就需要某种形式的同步.