C#中ConcurrentDictionary是否始终按线程顺序添加元素?
关于ConcurrentDictionary与BlockingCollection并行行为的疑问
我尝试使用BlockingCollection和ConcurrentDictionary,发现BlockingCollection会在线程完成后立即添加元素,但ConcurrentDictionary却始终按线程顺序添加。请问:
ConcurrentDictionary是等待前一个操作完成后才添加元素,还是添加后进行了排序?- 如果
ConcurrentDictionary按线程顺序添加元素,那么使用并行操作还有意义吗? - 为何
ConcurrentDictionary会有这样的行为,而BlockingCollection的表现却符合预期?
相关代码
BlockingCollection<string> blockingCollection = new BlockingCollection<string>(); ConcurrentDictionary<int, string> concurrentDictionary = new ConcurrentDictionary<int, string>(); var rand = new Random(); Parallel.For(0, 10, i => { int wait = rand.Next(10, 500); Thread.Sleep(wait); blockingCollection.Add($"t:{i} waittime:{wait}"); }); Parallel.For(0, 10, i => { int wait = rand.Next(10, 500); Thread.Sleep(wait); concurrentDictionary.TryAdd(i, $"t:{i} waittime:{wait}"); }); Console.WriteLine("--BlockingCollection--"); Console.WriteLine(string.Join("\n", blockingCollection.ToArray())); Console.WriteLine("--ConcurrentDictionary--"); Console.WriteLine(string.Join("\n", concurrentDictionary .Select(x => $"Key:{x.Key} Value:{x.Value}").ToArray()));
输出结果

问题解答
ConcurrentDictionary既不是等待前操作完成,也不是添加后排序
你看到的“按线程顺序”只是遍历的假象:ConcurrentDictionary基于哈希表实现,遍历默认按键的哈希值顺序输出。你用的键是连续整数0-9,它们的哈希值在默认实现下和键本身顺序一致,所以遍历结果看起来和线程序号顺序重合,但实际上TryAdd是并行执行的——不管哪个线程先完成任务,只要键唯一就会立刻插入,只是最后遍历的顺序和插入顺序无关。并行操作依然有意义
并行的核心是让多个耗时任务(比如代码里的Thread.Sleep模拟的业务逻辑)同时执行,总耗时会远低于串行执行(串行总耗时是所有等待时间的总和,并行则接近最长的那个等待时间)。ConcurrentDictionary只是提供线程安全的存储能力,它的遍历顺序不影响并行任务的执行效率,并行带来的性能提升依然存在。两者行为差异的本质原因
BlockingCollection是有序集合,它严格按照元素的插入顺序存储和遍历,所以先完成的线程添加的元素会排在前面,符合你对并行结果的预期。ConcurrentDictionary是哈希表结构,它的存储位置由键的哈希值决定,遍历顺序依赖哈希值而非插入顺序。如果换成随机生成的键,你就能看到遍历顺序和线程完成顺序完全不一致的结果。
内容的提问来源于stack exchange,提问作者Akif T
相关产品推荐
相关产品推荐

