使用和不使用多线程时,OpenMP For 循环产生不同的结果

OpenMP For-Loops yield different Results with and without Multithreading

我是多线程的新手,我在尝试并行处理一些 for 循环时发现了以下问题,我在其中操作 3D 数组。 当我 运行 代码仅使用单个线程时,我得到了我期望的 E_total 值。但是,当我将相同的代码与 多线程 和 OpenMP 一起使用时,我按以下方式设置 #pragma omp parallel for

    // DO FIRST COMPUTATION STEP ON 3D ARRAY
    #pragma omp parallel for
    for (size_t ix = 0; ix < N; ix++) {
        for (size_t iy = 0; iy < N; iy++) {
            for (size_t iz = 0; iz < N; iz++) {
                A1[ix][iy][iz] = ...;
            }
        }
    }

    // DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
    #pragma omp parallel for
    for (size_t ix = 0; ix < N; ix++) {
        for (size_t iy = 0; iy < N; iy++) {
            for (size_t iz = 0; iz < N; iz++) {
                A2[ix][iy][iz] = ...;
                E_pot += something * A1[ix][iy][iz];
                E_int += something * A2[ix][iy][iz];
            }
        }
    }
    E_total += (E_pot + E_int);    // This result changes when 'omp parallel for' is used

我发现 E_total 的结果不同。 由于循环操作是附加的或特定于网格点的(在不同 ijk 之间独立),它们不应依赖于循环内的任何顺序。

是否有可能在前面所有的第一个循环操作完成之前开始第二个for循环?如果是这样,我怎样才能避免这种情况或者我需要注意哪些其他错误?

对不起,如果这是一个非常基本的问题,但我无法在网上找到相关问题。提前致谢!

此代码的问题在于不同线程之间存在竞争条件。 E_potE_int 变量在工作线程之间共享,因此线程会不时破坏彼此的值。

要解决此问题,请应用 reduction 子句(请参阅 OpenMP API 规范中的 Reduction Clauses and Directives):

// DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
#pragma omp parallel for reduction(+:E_pot) reduction(+:E_int)
for (size_t ix = 0; ix < N; ix++) {
    for (size_t iy = 0; iy < N; iy++) {
        for (size_t iz = 0; iz < N; iz++) {
            A2[ix][iy][iz] = ...;
            E_pot += something * A1[ix][iy][iz];
            E_int += something * A2[ix][iy][iz];
        }
    }
}

还有一些您可以查看的更改,看看它们是否有帮助:

根据 N 的值,可能值得在 parallel for 指令中添加一个 collapse(2) 子句(参见 Worksharing-Loop Construct)以将两个外部循环合并到然后运行 ​​N*N 次迭代的单个循环。对于小 N,线程可以更好地工作,因为更多的迭代可以分布在工作线程中。

如果您显式添加 schedule(static)(当您什么都不说时,这是大多数 OpenMP 实现的默认设置,但在技术上不能保证),那么您可以将 nowait 添加到第一个循环.这样一来,在第一个并行循环结束时就没有隐式障碍,并且已经完成其工作块的线程可以继续进行第二个循环。 schedule(static) 是必需的,因为第一个和第二个循环都具有相同的并行化,然后该技巧就起作用了。注意:如果您为第一个循环添加了 collapse(2),那么第二个循环也需要具有 collapse(2),以便并行化相同。