数组并行化疑问:线程安全与循环优化方案咨询
循环并行优化问题
我刚接触并行化概念,了解到存在线程风险。现有如下C#循环代码:
//OceanVertexIndexes 是 List<int> 类型 //trisWithVertex 是 List<int>[] 类型 foreach (int coord in terrainFace.OceanVertexIndexes) for (int j = 0; j < trisWithVertex[coord].Count; ++j)//遍历包含当前顶点的三角形 { int value = trisWithVertex[coord][j]; int remainder = value % 3; trianglesDisabled[value - remainder] = true; trianglesDisabled[value - remainder + 1] = true; trianglesDisabled[value - remainder + 2] = true; }
其中OceanVertexIndexes是包含数百到数千元素的枚举集合,trianglesDisabled可能被多线程同时访问。请问:
- 这段循环能否进行并行优化?
- 若可以,采用拆分循环为两半调用
Parallel.Invoke,还是用Parallel.ForEach遍历的方式更合适?
备选代码如下:
//要不要这么做? Parallel.Invoke(/*()=>处理OceanVertexIndexes的前半部分, ()=>处理后半部分*/); //还是这么做? Parallel.ForEach(terrainFace.OceanVertexIndexes, coord => { for (int j = 0; j < trisWithVertex[coord].Count; ++j) { int value = trisWithVertex[coord][j]; int remainder = value % 3; trianglesDisabled[value - remainder] = true; trianglesDisabled[value - remainder + 1] = true; trianglesDisabled[value - remainder + 2] = true; } });
解答
1. 可以进行并行优化,但需注意内存可见性
这段代码的核心操作是给trianglesDisabled数组的元素赋值为true,不存在“读-改-写”的竞态条件(无论线程执行顺序如何,最终结果都是true)。不过从.NET内存模型的严谨性出发,建议使用Volatile.Write来确保修改对其他线程可见,避免因CPU缓存导致的不可见问题:
Volatile.Write(ref trianglesDisabled[value - remainder], true); Volatile.Write(ref trianglesDisabled[value - remainder + 1], true); Volatile.Write(ref trianglesDisabled[value - remainder + 2], true);
如果是x86/x64架构,bool赋值本身是原子操作,不做这个处理大概率也能正常运行,但加上会更稳妥。
2. 优先选择Parallel.ForEach,放弃手动拆分的Parallel.Invoke
原因如下:
- 自动负载均衡:
Parallel.ForEach会根据CPU核心数量、任务的实际负载自动分配线程和元素,不需要你手动拆分集合,代码更简洁,维护成本更低。 - 扩展性更好:如果后续
OceanVertexIndexes的元素数量变化,或者CPU核心数增加,Parallel.ForEach能自动适应,而手动拆分的Parallel.Invoke需要你重新调整拆分逻辑,否则会出现负载不均(比如元素数为奇数时两半元素数量不等),浪费CPU资源。 - 代码更简洁:无需编写集合拆分的额外逻辑,直接复用现有枚举集合即可。
如果trisWithVertex中各个子列表的长度差异极大,导致默认的分区策略效率不高,可以考虑使用Partitioner.Create自定义分区,但对你当前数百到数千元素的场景来说,默认的Parallel.ForEach已经足够高效。
内容的提问来源于stack exchange,提问作者Stefan Carpeliuc
相关产品推荐
相关产品推荐

