信息发布→ 登录 注册 退出

c# ImmutableArray 和 List 加锁的性能和场景对比

发布时间:2026-01-07

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

和 list 加锁的性能和场景对比">

ImmutableArray 不需要加锁,但写操作代价高

ImmutableArray 是不可变结构体,所有“修改”操作(如 AddSetItem)都返回新实例,底层复制整个数组。它本身线程安全——读取无需加锁,多个线程同时读不会出错,也不存在竞态。但如果你频繁调用 array.Add(item) 并期望“累积修改”,实际是在反复分配新数组,GC 压力和内存拷贝开销会迅速上升。

常见错误现象:
• 用 for 循环反复 Add 构建集合,性能比 List 慢数十倍
• 误以为 ImmutableArray 是“高性能无锁集合”,却在写密集场景滥用

适用场景:
• 配置数据、查找表等初始化后极少变更的只读集合
• 函数式风格管道处理(如 .Select(...).Where(...).ToArray() 后转为 ImmutableArray 固化结果)
• 需要值语义比较(==.Equals 判断内容是否相同)

List 加锁是唯一线程安全写方式,但粒度很关键

List 本身不是线程安全的。多线程同时调用 AddRemoveAt 或遍历中修改,会触发 InvalidOperationException(“集合已修改”)或静默数据损坏。必须显式加锁,但锁的范围直接影响吞吐量。

容易踩的坑:
• 对整个 List 操作都用同一个 lock(obj),变成串行执行,失去并发意义
• 在 foreach 遍历时加锁,但未覆盖全部读路径(比如其他地方有裸读),仍可能遇到 Collection was modified
• 锁对象暴露给外部(如 lock(list)),引发死锁或意外争用

实操建议:
• 用私有 readonly object 字段做锁对象:private readonly object _sync = new();
• 写操作(AddClear)必须锁;纯读(Countthis[index])可不锁,但需接受可能看到“过期”状态
• 若写操作占比 >10%,考虑改用 ConcurrentBagConcurrentQueue 等真正并发集合

性能对比:小数据量差异不大,大数据量写操作 ImmutableArray 明显更慢

在 1000 个元素内,ImmutableArray.Add 和加锁 List.Add 的单次耗时差距不明显(纳秒级),但前者每次分配新数组,后者只是扩容(摊还 O(1))。当循环添加 10 万次:

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(索引访问、SortBinarySearch
• 并发度不高(如 2~4 个生产者),锁争用可控

容易被忽略的点:
ImmutableArray.ToBuilder() 返回可变包装器,内部仍用数组,适合“批量构建+固化”场景,比反复 Add 高效得多
• 如果读远多于写,又要求强一致性,ReaderWriterLockSlim 比简单 lock 更合适,但复杂度上升

标签:# Collection  # 遍历  # 已有  # 不需要  # 多个  # 是在  # 也不  # 如果你  # 极少  # 死锁  # 加锁  # this  # 对象  # 并发  # 多线程  # 线程  # js  # Struct  # private  # 循环  # 结构体  # select  # foreach  # for  # sort  # count  # Object  # Array  # 无锁  # c#  # 大数据  # json  
在线客服
服务热线

服务热线

4008888355

微信咨询
二维码
返回顶部
×二维码

截屏,微信识别二维码

打开微信

微信号已复制,请打开微信添加咨询详情!