在Parallel.ForEach中共享List<T>是否需要同步?两种场景解析
Parallel.ForEach操作List的同步必要性分析
仅调用Add添加元素的场景
别侥幸,必须加同步或者换线程安全集合,List<T>完全不是线程安全的。
Add看着是个单一方法,但内部要做的事不少:检查当前数组容量够不够,不够就扩容;然后把元素放到对应位置,再更新Count字段。这些步骤没有原子性保障,多个线程并行执行时,会出现各种竞态问题:比如两个线程同时触发扩容,导致数组状态混乱;或者更新Count时互相覆盖,最后集合里的元素数量不对,甚至直接抛出IndexOutOfRangeException。
先Any读取再Add的场景
这个场景风险比单纯Add还高,绝对需要同步。
这里同时涉及了读(Any)和写(Add)操作,List<T>根本不支持多线程下的读写并发。当一个线程在执行Any遍历集合的时候,另一个线程可能正在修改内部数组(比如扩容、插入元素),轻则导致Any返回错误结果(比如集合有元素却返回false),重则直接抛出InvalidOperationException(提示集合已修改,无法枚举)。哪怕你觉得Any只是判断是否为空,它本质还是在遍历集合,只要遍历过程中集合被修改,就会出问题。
总结
不管是单纯写还是读写混合,只要是在Parallel.ForEach这种多线程并行场景下操作List<T>,都不能直接裸用。要么用lock之类的同步机制包裹操作,要么直接换成ConcurrentBag<T>、ConcurrentQueue<T>这类线程安全集合。
内容的提问来源于stack exchange,提问作者pmn
相关产品推荐
相关产品推荐

