OpenMP 返回错误结果

Otá*_*ima 2 c++ parallel-processing performance multithreading openmp

下面的代码应该使用 4 个线程来计算 0 到 1000 之间所有数字的总和。它应该返回 499500,但在每次执行时返回不同的值。

#include <omp.h>
#include <iostream>

using namespace std;

int main (int argc, char *argv[])
{
        int nthreads, i, tid;
        float total;

        #pragma omp parallel num_threads(4) 
        {
            tid = omp_get_thread_num();
            if (tid == 0) {
                nthreads = omp_get_num_threads();
                cout << "Número de threads = " << nthreads <<endl;
            }
           #pragma omp barrier

           total = 0.0;
           #pragma omp for schedule(dynamic,10) private(i)
           for (i=0; i<1000; i++)
               total = total + i*1.0;
        } 
        cout << "Total = " <<total << endl;
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

dre*_*ash 5

您的代码中发生的情况是您有多个线程同时修改变量的值total。为了解决这个问题,您可以使用 OpenMPreduction子句,从OpenMP 标准可以阅读:

减少子句可用于并行执行某些形式的递归计算(...)。对于并行和工作共享结构,会创建每个列表项的私有副本,每个隐式任务都有一个副本,就像使用了私有子句一样。(...) 然后按照上面的规定初始化私有副本。在指定了归约子句的区域的末尾,通过使用指定归约标识符的组合器将其原始值与每个私有副本的最终值进行组合来更新原始列表项。

有关减少子句如何工作的更详细说明,请查看 this SO Thread

因此,要解决代码中的竞争条件,只需将其更改为:

        #pragma omp for schedule(dynamic,10) private(i) reduction(+:total)
        for (i=0; i<1000; i++)
             total = total + i*1.0;
Run Code Online (Sandbox Code Playgroud)

应用reduce子句后,为避免错误结果,不要total在并行区域内初始化共享变量:

total = 0.0;
Run Code Online (Sandbox Code Playgroud)

只需将其设置在并行区域之前。

关于变量tid本身还有另一个竞争条件,它在线程之间共享并在并行区域内并发更新:

tid = omp_get_thread_num();
Run Code Online (Sandbox Code Playgroud)

这可以通过tid将每个线程设为私有来解决,例如:

int tid = omp_get_thread_num();
Run Code Online (Sandbox Code Playgroud)

旁注

在 OpenMP 中,由 包围的最外层循环#pragma omp for将使其索引变量(i在此上下文中)已经私有,因此该子句private(i)并不是真正必要的。

另一点是schedule(dynamic,10); 除非你只是在玩,否则使用 schedule 实际上更有意义(性能方面)static,因为代码不会导致负载平衡问题。该dynamic计划具有的额外开销分配在运行时的任务线程,而在分配static计划在编译时进行。

最后,以下内容:

    tid = omp_get_thread_num();
    if (tid == 0) {
        nthreads = omp_get_num_threads();
        cout << "Número de threads = " << nthreads <<endl;
    }
Run Code Online (Sandbox Code Playgroud)

可以通过使用OpenMP master 子句来简化,即:

    #pragma omp master
    {
        nthreads = omp_get_num_threads();
        cout << "Número de threads = " << nthreads <<endl;
    }
Run Code Online (Sandbox Code Playgroud)

应用了更改的正在运行的解决方案:

#include <omp.h>
#include <iostream>

using namespace std;

int main (int argc, char *argv[])
{
        float total = 0.0;

        #pragma omp parallel num_threads(4) 
        {
           #pragma omp master
           {
                int nthreads = omp_get_num_threads();
                cout << "Número de threads = " << nthreads <<endl;
           }
           #pragma omp barrier

           #pragma omp for reduction(+:total)
           for (int i=0; i < 1000; i++)
               total = total + i*1.0;
        } 
        cout << "Total = " <<total << endl;
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

Número de threads = 4
Total = 499500
Run Code Online (Sandbox Code Playgroud)