C#中Parallel.ForEach抛索引越界异常但foreach正常的问题
问题描述
我写的程序需要检查数组所有元素的状态,用foreach运行正常,但换成Parallel.ForEach或Parallel.For就会抛出索引越界异常。我没有修改集合_currentSpherePoints,里面是自定义的Vector类,不知道问题出在哪。
正常运行的foreach代码:
foreach (var p in _currentSpherePoints) { point = p + center; point.X = (point.X + _field.GetLength((int)VectorInt.Dimension.X)) % _field.GetLength((int)VectorInt.Dimension.X); point.Y = (point.Y + _field.GetLength((int)VectorInt.Dimension.Y)) % _field.GetLength((int)VectorInt.Dimension.Y); point.Z = (point.Z + _field.GetLength((int)VectorInt.Dimension.Z)) % _field.GetLength((int)VectorInt.Dimension.Z); if (_field[point.X, point.Y, point.Z] != 0) { isCrossed = true; break; } }
抛出异常的Parallel.ForEach代码:
public bool IsSphereCrossed(VectorInt center) { bool isCrossed = false; var point = new VectorInt(0, 0, 0); Parallel.ForEach(_currentSpherePoints, (p, state) => { point = p + center; point.X = (point.X + _field.GetLength((int)VectorInt.Dimension.X)) % _field.GetLength((int)VectorInt.Dimension.X); point.Y = (point.Y + _field.GetLength((int)VectorInt.Dimension.Y)) % _field.GetLength((int)VectorInt.Dimension.Y); point.Z = (point.Z + _field.GetLength((int)VectorInt.Dimension.Z)) % _field.GetLength((int)VectorInt.Dimension.Z); if (_field[point.X, point.Y, point.Z] != 0) { isCrossed = true; state.Stop(); } }); return isCrossed; }
问题原因
- 共享变量竞争:
point是在Parallel.ForEach外部声明的共享变量,多个并行线程会同时读写这个变量。比如线程A刚计算完point.X,线程B就把point重新赋值为另一个向量,导致线程A后续计算point.Y、point.Z时用的是线程B的向量值,最终生成的坐标完全混乱,超出_field的索引范围,直接触发越界异常。 isCrossed的线程安全隐患:多个线程同时修改这个布尔变量,虽然布尔赋值是原子操作,但结合state.Stop()的逻辑,可能出现线程设置isCrossed后,其他线程还在继续执行的情况,不过这不是索引越界的直接原因。
修复方案
方案1:使用局部变量避免共享竞争
把point移到Parallel.ForEach的委托内部,让每个线程都使用自己独立的局部变量,同时用Interlocked保证isCrossed的线程安全赋值:
public bool IsSphereCrossed(VectorInt center) { bool isCrossed = false; Parallel.ForEach(_currentSpherePoints, (p, state) => { // 每个线程单独创建point变量,避免跨线程竞争 var point = p + center; int xLength = _field.GetLength((int)VectorInt.Dimension.X); point.X = (point.X + xLength) % xLength; int yLength = _field.GetLength((int)VectorInt.Dimension.Y); point.Y = (point.Y + yLength) % yLength; int zLength = _field.GetLength((int)VectorInt.Dimension.Z); point.Z = (point.Z + zLength) % zLength; if (_field[point.X, point.Y, point.Z] != 0) { // 原子操作赋值,确保线程安全 Interlocked.Exchange(ref isCrossed, true); state.Stop(); } }); return isCrossed; }
方案2:提前缓存维度长度优化性能
可以提前缓存_field的各维度长度,避免在并行循环中重复调用GetLength,进一步提升效率:
public bool IsSphereCrossed(VectorInt center) { bool isCrossed = false; // 提前缓存维度长度,减少重复计算 int xLength = _field.GetLength((int)VectorInt.Dimension.X); int yLength = _field.GetLength((int)VectorInt.Dimension.Y); int zLength = _field.GetLength((int)VectorInt.Dimension.Z); Parallel.ForEach(_currentSpherePoints, (p, state) => { var point = p + center; point.X = (point.X + xLength) % xLength; point.Y = (point.Y + yLength) % yLength; point.Z = (point.Z + zLength) % zLength; if (_field[point.X, point.Y, point.Z] != 0) { Interlocked.Exchange(ref isCrossed, true); state.Stop(); } }); return isCrossed; }
内容的提问来源于stack exchange,提问作者Kay Altos
相关产品推荐
相关产品推荐

