ImmutableArray 无需加锁即可线程安全读取,但每次写操作都复制整个数组,性能远低于加锁的 List;适用场景为初始化后极少修改的只读集合或需值语义比较的场合。

ImmutableArray 不需要加锁,但写操作代价高
ImmutableArrayAdd、SetItem)都返回新实例,底层复制整个数组。它本身线程安全——读取无需加锁,多个线程同时读不会出错,也不存在竞态。但如果你频繁调用 array.Add(item) 并期望“累积修改”,实际是在反复分配新数组,GC 压力和内存拷贝开销会迅速上升。
常见错误现象:
• 用 for 循环反复 Add 构建集合,性能比 List 慢数十倍
• 误以为 ImmutableArray 是“高性能无锁集合”,却在写密集场景滥用
适用场景:
• 配置数据、查找表等初始化后极少变更的只读集合
• 函数式风格管道处理(如 .Select(...).Where(...).ToArray() 后转为 ImmutableArray 固化结果)
• 需要值语义比较(== 或 .Equals 判断内容是否相同)
List 加锁是唯一线程安全写方式,但粒度很关键
List 本身不是线程安全的。多线程同时调用 Add、RemoveAt 或遍历中修改,会触发 InvalidOperationException(“集合已修改”)或静默数据损坏。必须显式加锁,但锁的范围直接影响吞吐量。
容易踩的坑:
• 对整个 List 操作都用同一个 lock(obj),变成串行执行,失去并发意义
• 在 foreach 遍历时加锁,但未覆盖全部读路径(比如其他地方有裸读),仍可能遇到 Collection was modified
• 锁对象暴露给外部(如 lock(list)),引发死锁或意外争用
实操建议:
• 用私有 readonly object 字段做锁对象:private readonly object _sync = new();
• 写操作(Add、Clear)必须锁;纯读(Count、this[index])可不锁,但需接受可能看到“过期”状态
• 若写操作占比 >10%,考虑改用 ConcurrentBag 或 ConcurrentQueue 等真正并发集合
性能对比:小数据量差异不大,大数据量写操作 ImmutableArray 明显更慢
在 1000 个元素内,ImmutableArray 和加锁 List 的单次耗时差距不明显(纳秒级),但前者每次分配新数组,后者只是扩容(摊还 O(1))。当循环添加 10 万次:
XpShop商城系统是新普软件开发有限公司针对大型连锁超市、百货公司、网上大卖场推出的一款结合ERP库存管理的网上商店系统,网上商城系统,也是新普软件公司大型电子商务解决方案中的一款软件产品。 XpShop v2012版本采用.net framework 3.5,mssql 2005,系统框架重新设计,功能更加的强大,访问速度和系统性能都得到了很大的提升。此外,秉承"简单体验科技&qu
var list = new List(); var array = ImmutableArray .Empty; var sw = Stopwatch.StartNew(); // List + lock for (int i = 0; i < 100000; i++) { lock (_sync) list.Add(i); } sw.Restart(); // ImmutableArray for (int i = 0; i < 100000; i++) { array = array.Add(i); // 每次都 new int[i+1] 并 copy }
后者实际执行约 50 亿次数组元素拷贝(1+2+3+…+100000 ≈ 5e9),而前者仅约 17 次扩容(2→4→8→…→131072),耗时差可达 100 倍以上。
参数差异:
• ImmutableArray 构造成本低(可由 Array.AsImmutable() 零拷贝创建),但写成本高
• List 构造无开销,写成本低,但并发写必须自行保证同步
真正该选哪个?看数据生命周期而非“是否要锁”
别纠结“哪个更快加锁”,先问:这个集合的典型使用模式是什么?
选 ImmutableArray 当:
• 数据构建一次,后续只读(如解析 JSON 后的配置项缓存)
• 需要跨线程传递且不允许被意外修改(避免防御性克隆)
• 要利用其 struct 特性减少 GC(但注意:大数组传参会复制)
选加锁 List 当:
• 写操作频繁且无法预估总量(如实时日志缓冲区)
• 已有代码重度依赖 List API(索引访问、Sort、BinarySearch)
• 并发度不高(如 2~4 个生产者),锁争用可控
容易被忽略的点:
• ImmutableArray 的 .ToBuilder() 返回可变包装器,内部仍用数组,适合“批量构建+固化”场景,比反复 Add 高效得多
• 如果读远多于写,又要求强一致性,ReaderWriterLockSlim 比简单 lock 更合适,但复杂度上升










