OpenMP:当我通过线程ID访问共享变量时,是否需要临界区?

3

我正在使用OpenMP来并行化for循环。我尝试通过线程ID访问C++ Armadillo向量,但我想知道即使不同的线程访问不重叠的内存区域,我是否必须将访问放在关键部分中。

这是我的代码:

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

int main()
{

        arma::mat A = arma::randu<arma::mat>(1000,700);
        arma::rowvec point = A.row(0);
        arma::vec distances = arma::zeros(omp_get_max_threads());

        #pragma omp parallel shared(A,point,distances)
        {

                arma::vec local_distances = arma::zeros(omp_get_num_threads());
                int thread_id = omp_get_thread_num();

                for(unsigned int l = 0; l < A.n_rows; l++){
                        double temp = arma::norm(A.row(l) - point,2);
                        if(temp > local_distances[thread_id])
                                local_distances[thread_id] = temp;
                }

                // Is it necessary to put a critical section here?
                #pragma omp critical 
                if(local_distances[thread_id] > distances[thread_id]){
                        distances[thread_id] = local_distances[thread_id];
                }

        }

        std::cout << distances[distances.index_max()] << std::endl;

}

在我的情况下,把读/写操作放到distances向量中是必要的吗?


1
不,这不需要临界区。 - ssb
2个回答

3

您的代码没问题。重要的是要理解以下内容:

  • 在并行区域之外声明的变量隐式地被视为 shared
  • 在并行区域之内声明的变量隐式地被视为 private,因此每个线程都有其本地副本。

因此,为每个线程声明一个私有距离向量并不实用。甚至不需要单独使用 local_distances,因为可以正确访问 distances。(尽管应该注意到,访问 distances 的效率非常低,因为不同的线程将尝试在同一高速缓存线上写入数据)。无论如何,整个过程称为归约,OpenMP 对此有很好的支持。您可以按以下方式编写此代码:

arma::mat A = arma::randu<arma::mat>(1000,700);
arma::rowvec point = A.row(0);
double distance = 0.;
#pragma omp parallel reduction(max:distance)
{
        for(unsigned int l = 0; l < A.n_rows; l++){
                distance = std::max(distance, arma::norm(A.row(l) - point,2));
        }
}
std::cout << distance << std::endl;

声明一个名为reduction的变量意味着每个线程都有一个本地副本,在并行区域之后,将应用缩减操作到本地副本集合中。这是最简洁、惯用和性能最优的解决方案。
附注:使用C++代码时,有时可能比较难确定访问是否安全,例如通过operator[]arma::mat::row进行访问在多线程程序中是否安全。您必须始终确定您的代码是否涉及从共享数据进行写入和/或读取。只有一个线程可以进行独占写入多个线程可以进行读取。

1
谢谢!实际上我不得不修改我的代码,以便能够获取 A 中具有最大距离的行的索引。因此,我不仅需要最大距离,还需要该行的索引。我应该更新我的问题还是发布另一个帖子?但我不确定这种情况下是否可以使用 reduction 变量来处理。 - Cauchy
用户定义的约简通常被推荐用于最大元素,尽管使用最大约简和lastprivate(使用C而不是C ++)的组合通常效果更好。 - tim18
请查看此答案以了解如何使用OpenMP用户定义的归约执行argmax:https://stackoverflow.com/a/42771196/620382 - Zulan

2

多线程编程的难点在于需要处理共享的可变状态。一个线程访问可变(可改变)数据并没有什么问题,或者多个线程同时访问不可变(常量)数据也没有问题。只有当多个线程需要访问同一可变数据时,才需要进行同步/临界区操作。

你的代码属于第一种情况,因为每个线程ID都索引到唯一的数据--每次只有一个线程更改数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接