Parallel.ForEach搜索无法返回正确值,如何解决并行查找最值错误问题
问题根因
- 存在竞态条件:现有代码计算
Math.Min(pMin, min)、Math.Max(pMax, max)时,读取的全局pMin/pMax为瞬时值,从读取完成到执行Interlocked.Exchange的间隙,其他线程可能已经更新了全局变量,导致本次写入的结果基于旧值计算,会覆盖其他线程已经写入的更小值/更大值,最终结果错误。 - 类型转换不安全:使用
Unsafe.As<short, int>将2字节的short强转为4字节的int执行原子操作,存在内存越界访问风险,可能触发不可预知的异常。
修复方案
推荐使用Parallel.ForEach自带的本地线程状态聚合重载,无需手动处理复杂的原子操作,性能更高且无竞态问题。该方案逻辑为:每个分区线程先独立计算自己负责区间的最大最小值,所有分区计算完成后再统一合并全局结果,锁竞争极低。
修复后的并行计算段代码如下:
// 生成测试列表、顺序搜索部分代码保持不变,仅替换并行搜索逻辑 short pMin = short.MaxValue; short pMax = 0; var rangePortioner = Partitioner.Create(0, test.Count); object lockObj = new object(); timer.Restart(); Parallel.ForEach( rangePortioner, // 初始化每个线程的本地最大最小值 () => (LocalMin: short.MaxValue, LocalMax: (short)0), // 分区内遍历,仅更新本地变量,无全局竞争 (range, loopState, localState) => { for (int i = range.Item1; i < range.Item2; i++) { localState.LocalMin = Math.Min(localState.LocalMin, test[i]); localState.LocalMax = Math.Max(localState.LocalMax, test[i]); } return localState; }, // 合并每个线程的本地结果到全局变量 (localState) => { lock (lockObj) { pMin = Math.Min(pMin, localState.LocalMin); pMax = Math.Max(pMax, localState.LocalMax); } }); timer.Stop();
如果坚持使用Interlocked实现,需要改为循环判断更新的逻辑,避免竞态:
// 最小值更新逻辑,最大值同理 int oldVal, newVal; do { oldVal = pMin; newVal = Math.Min(oldVal, min); } while (Interlocked.CompareExchange(ref Unsafe.As<short, int>(ref pMin), newVal, oldVal) != oldVal);
内容的提问来源于stack exchange,提问作者master_ruko
相关产品推荐
相关产品推荐

