list直接加lock不够用,因其迭代器不检查外部修改且无内置线程协作机制;synchronizedcollection不保证枚举安全;高并发应依读写比例选concurrentbag、concurrentdictionary或immutablelist等合适工具。

为什么 List 直接加 lock 不够用?
很多人遇到多线程往 List<t></t> 里 Add 或遍历就崩溃,第一反应是“我加个 lock 不就行了”——但实际会踩两个坑:一是遍历时被其他线程 Modify 导致 InvalidOperationException: Collection was modified;二是即使所有操作都套了 lock,一旦漏掉某处(比如忘了锁 GetEnumerator),依然会崩。根本原因是 List<t></t> 的迭代器本身不检查外部修改,且内部状态无保护。它不是“线程不安全”的问题,而是“完全没设计线程协作机制”。
SynchronizedCollection<t></t> 真的能直接替代 List 吗?
不能。它只在特定场景下安全:仅当所有读写都通过它的公开方法(Add、RemoveAt、Count、索引器)进行时,才保证原子性。但它不提供线程安全的枚举——foreach 调用的是底层 IEnumerable<t></t>,而它的 GetEnumerator() 返回的是非同步包装器,仍可能在遍历时被修改。常见错误写法:
var syncList = new SynchronizedCollection<string>();
// ✅ 安全
syncList.Add("a");
// ❌ 危险!枚举过程无锁保护
foreach (var s in syncList) { ... }</string>
如果你需要频繁遍历,必须手动加锁或改用其他结构。
真正适合高并发读写的替代方案有哪些?
根据读写比例和一致性要求选:
-
读多写少 + 弱一致性容忍:用
ConcurrentBag<t></t>或ConcurrentQueue<t></t>,它们无索引访问,但 Add/Peek/TryDequeue 全线程安全,无锁开销低 -
需要索引 + 读写均衡:用
ConcurrentDictionary<int t></int>模拟数组(key 为 index),或封装ReaderWriterLockSlim+List<t></t>,读时允许多个线程,写时独占 -
强一致性 + 频繁遍历:别硬扛,改用不可变集合——
ImmutableList<t>.Add()</t>返回新实例,天然无竞态,但注意内存分配成本
没有“万能线程安全 List”,只有“匹配你读写模式的正确工具”。
用 lock 包裹 List<t></t> 时最容易漏什么?
三个高频遗漏点:
- 忘了锁
Count属性访问——if (list.Count > 0) list.RemoveAt(0);是典型的竞态窗口,必须把整个判断+操作包进同一个lock - 返回
List<t></t>引用给外部(如 public getter),调用方可能绕过锁直接操作原列表 - 异步方法中用
await后继续操作列表——lock不跨 await,await 后可能已换线程,原锁失效
最稳妥的做法:把 List<t></t> 设为 private readonly,所有访问都走带锁的 public 方法,且方法内不暴露内部引用。











