Microsoft的Parallel.For文档包含以下方法:
static void MultiplyMatricesParallel(double[,] matA, double[,] matB, double[,] result) { int matACols = matA.GetLength(1); int matBCols = matB.GetLength(1); int matARows = matA.GetLength(0); // A basic matrix multiplication. // Parallelize the outer loop to partition the source array by rows. Parallel.For(0, matARows, i => { for (int j = 0; j < matBCols; j++) { double temp = 0; for (int k = 0; k < matACols; k++) { temp += matA[i, k] * matB[k, j]; } result[i, j] = temp; } }); // Parallel.For }
在此方法中,可能有多个线程从调用线程读取matA
和matB
创建并初始化的值,并且可能有多个线程将值写入result
,稍后由调用线程读取.在传递给lambda的范围内Parallel.For
,数组的读写没有明确的锁定.因为这个例子来自微软,我认为它是线程安全的,但我试图了解幕后发生的事情,使其成为线程安全的.
根据我所阅读的内容以及我在SO上提出的其他问题(例如本文),我需要考虑几个内存障碍才能使这一切顺利进行.那些是:
调用线程上的内存屏障创建和intializing后matA
和matB
,
在每个非调用线程的存储器阻挡从读取值之前matA
和matB
,
写入值后,每个非调用线程上的内存屏障result
,和
在读取值之前调用线程上的内存屏障result
.
我理解正确吗?
如果是这样,那么Parallel.For
以某种方式做到了吗?我去了参考源,但是在编写代码时遇到了麻烦.我没有看到任何lock
阻止或MemoryBarrier
通话.
由于数组已经创建,因此写入或读取数组不会导致任何调整大小.此外,代码本身也阻止在数组中读/写相同的位置.
底线是代码总是可以计算在数组中读取和写入的位置,并且这些调用永远不会相互交叉.因此,它是线程安全的.
您正在寻找的内存障碍位于任务调度程序中.
ParallelFor将工作分解为任务,并且工作窃取调度程序执行这些任务.工作窃取调度程序所需的最小内存障碍是:
创建任务后的"释放"栏.
任务被盗时"获取"围栏.
被盗任务完成时的"释放"围栏.
正在等待任务的线程的"获取"栅栏.
看看这里的其中1被用来排队任务的原子("联锁")操作暗示.在这里查看任务被盗时原子操作,易失性读取和/或锁定所隐含的2.
我无法追踪3和4的位置.某种原子连接计数器可能暗示了3和4.