sortedList 和 SortedDictionary 默认非线程安全,多线程读写会引发异常或数据不一致;加锁需覆盖所有访问路径且防死锁;推荐用 ConcurrentDictionary 配合手动排序实现安全有序操作。

SortedList 和 SortedDictionary 默认都不是线程安全的
直接在多线程环境下读写同一个 SortedList 或 SortedDictionary 实例,会触发 InvalidOperationException(如“集合已修改;枚举操作可能无法执行”),或产生数据不一致、索引越界等未定义行为。.net 官方文档明确标注二者均 不保证线程安全,即使只读访问多个线程同时遍历,也可能因底层结构被其他线程修改而失败。
为什么不能靠加锁“简单包一层”就高枕无忧
加锁能避免崩溃,但容易忽略两个关键点:
- 所有访问路径(包括
ContainsKey、TryGetValue、Keys、Values、GetEnumerator())都必须使用同一把锁,漏掉任意一个就可能出问题 -
Keys和Values属性返回的是动态视图,不是快照——如果在遍历myDict.Keys时另一个线程修改了字典,迭代器立刻失效 - 嵌套调用(比如在锁内调用一个外部方法,而该方法又间接访问了同一个字典)可能引发死锁或锁粒度失控
常见错误写法:
lock (_lock) { if (dict.ContainsKey(key)) { // ✅ 加锁 dict[key] = value; // ✅ 加锁 } } // 但下面这行没锁,且返回的 ICollection 不是线程安全的: var keys = dict.Keys.ToList(); // ❌ 可能中途被改,ToList() 过程中抛异常
真正安全的替代方案:ConcurrentDictionary + 手动排序逻辑
ConcurrentDictionary 是唯一内置线程安全的键值集合,但它不维持顺序。若业务强依赖有序遍历(如按 key 升序取前 N 项),只能放弃自动排序,改用以下组合:
- 用
ConcurrentDictionary存储数据,保障并发读写安全 - 需要有序结果时,临时提取
Keys或ToArray(),再用OrderBy()或Array.Sort()排序(注意:这是快照,不影响原集合) - 若频繁按范围查询(如 “key ∈ [a,b)”),可额外维护一个
ConcurrentBag+ 定期重建排序数组,或引入第三方库如System.Collections.Concurrent.ConcurrentSortedList(非 .NET 内置,需 NuGet)
示例(安全取最小 key 对应的 value):
var keys = concurrentDict.Keys.ToArray(); if (keys.Length > 0) { var minKey = keys.Min(); // 或 Array.Sort(keys); var minKey = keys[0]; concurrentDict.TryGetValue(minKey, out var value); }
SortedDictionary 在高并发写入下的性能陷阱
虽然 SortedDictionary 基于红黑树,插入/查找平均 O(log n),但它的内部节点操作不是原子的。多线程写入时,即使加了锁,也会因锁竞争导致吞吐量急剧下降——尤其当写操作占比超过 20%,性能可能比单线程还差。相比之下,ConcurrentDictionary 的分段锁机制在写密集场景下更稳定。
容易被忽略的一点:SortedList 在大量插入后内存碎片更严重(底层是数组),扩容时需复制整个数组,此时锁持有时间变长,进一步加剧争用。除非你确定是读多写少 + 数据量小(IList