并行内存障碍

adv*_*v12 6 c# multithreading memory-barriers parallel.for

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
}
Run Code Online (Sandbox Code Playgroud)

在此方法中,可能有多个线程从调用线程读取matAmatB创建并初始化的值,并且可能有多个线程将值写入result,稍后由调用线程读取.在传递给lambda的范围内Parallel.For,数组的读写没有明确的锁定.因为这个例子来自微软,我认为它是线程安全的,但我试图了解幕后发生的事情,使其成为线程安全的.

根据我所阅读的内容以及我在SO上提出的其他问题(例如本文),我需要考虑几个内存障碍才能使这一切顺利进行.那些是:

  1. 调用线程上的内存屏障创建和intializing后matAmatB,
  2. 在每个非调用线程的存储器阻挡从读取值之前matAmatB,
  3. 写入值后,每个非调用线程上的内存屏障result,和
  4. 在读取值之前调用线程上的内存屏障result.

我理解正确吗?

如果是这样,那么Parallel.For以某种方式做到了吗?我去了参考源,但是在编写代码时遇到了麻烦.我没有看到任何lock阻止或MemoryBarrier通话.

Pat*_*man 6

由于数组已经创建,因此写入或读取数组不会导致任何调整大小.此外,代码本身也阻止在数组中读/写相同的位置.

底线是代码总是可以计算在数组中读取和写入的位置,并且这些调用永远不会相互交叉.因此,它是线程安全的.

  • @PatrickHofman,对,我要问的是,什么保证调用线程看到所有并行线程写入的值?我认为,在某种程度上,一定存在锁定或内存障碍;我想知道在哪里以及如何。 (2认同)

Arc*_*son 6

您正在寻找的内存障碍位于任务调度程序中.

ParallelFor将工作分解为任务,并且工作窃取调度程序执行这些任务.工作窃取调度程序所需的最小内存障碍是:

  1. 创建任务后的"释放"栏.
  2. 任务被盗时"获取"围栏.
  3. 被盗任务完成时的"释放"围栏.
  4. 正在等待任务的线程的"获取"栅栏.

看看这里的其中1被用来排队任务的原子("联锁")操作暗示.这里查看任务被盗时原子操作,易失性读取和/或锁定所隐含的2.

我无法追踪3和4的位置.某种原子连接计数器可能暗示了3和4.


Hen*_*man 3

在线程(实际上是:任务)内部对 matA 和 matB 的访问是只读的,结果是只写的。

并行读取本质上是线程安全的,写入也是线程安全的,因为i每个任务的变量都是唯一的。

这段代码中不需要内存屏障(除了整个 Parallel.For 之前/之后,但可以假设这些)。

从您编号的项目来看,
Parallel.For() 暗示了 1) 和 4),
根本不需要 2) 和 3)。

  • 感谢你的回答。如果不需要(2)和(3),那么什么保证在并行线程上完成的读取看到初始化值(而不是例如它们在缓存行中碰巧拥有的一些旧数据),以及什么保证在并行线程上完成的写入对调用线程可见(除了 Windows 上的 .NET 碰巧将写入视为易失性的实现细节之外)?基本上,我的印象是两个线程之间的内存可见性取决于*两个*线程上的内存屏障,一个在写入之后,一个在读取之前。 (2认同)
  • 如果知道他们不这样做,那将是非常令人鼓舞的。但我的印象是,由于 CPU 缓存等原因,它们可能对调用线程来说似乎已满,但对并行线程来说却没有,并且需要锁定或内存屏障来保证并行线程看到调用线程写入的内容他们,即使那发生在线程之前。就像我说的,我很想确信事实并非如此。如果是这样,线程代码对于我这种智力水平的人来说太复杂了,无法编写。 (2认同)