std::lock_guard 和 #pragma omp critical 之间的区别

jri*_*jri 6 c++ performance multithreading mutex openmp

让我们考虑一些代码,以便在具有多个线程的for循环中安全地递增变量。

为此,您必须在增加变量时使用某种锁定机制。当我在寻找解决方案时,我想出了以下解决方案。

我的问题是:

  1. 它们是否同样好,还是其中之一有一些后备?
  2. 何时使用 amutex而不是#pragma omp critical?
#include <iostream>
#include <mutex>

int main(int argc, char** argv)
{
    int someVar = 0;
    std::mutex someVar_mutex;

    #pragma omp parallel for
    for (int i = 0; i < 1000; i++)
    {
        std::lock_guard<std::mutex> lock(someVar_mutex);
        ++someVar;
    }

    std::cout << someVar << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)
#include <iostream>

int main(int argc, char** argv)
{
    int someVar = 0;

    #pragma omp parallel for
    for (int i = 0; i < 1000; i++)
    {
        #pragma omp critical
        ++someVar;
    }

    std::cout << someVar << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

StP*_*ere 5

临界区的作用与获取锁相同(并且可能会在内部使用锁)。

  1. std::mutex是标准 C++ 功能,而是#pragma omp criticalOpenMP 扩展且未由标准定义。

  2. 临界区名称对于整个程序来说是全局的(无论模块边界如何)。因此,如果多个模块中有一个同名的关键部分,则不能同时执行其中两个。如果省略名称,则采用默认名称。(文档)。

更喜欢标准 C++,除非有充分的理由使用另一个(在测量两者之后)。

不是直接针对问题,但这个循环还存在另一个问题:锁在每次循环迭代时执行。这会显着降低性能(另请参阅此答案)。


dre*_*ash 5

cppreference.com关于lock_guard可以读到

lock_guard 类是一个互斥锁包装器,它提供了一种方便的 RAII 式机制,用于在作用域块的持续时间内拥有互斥锁。

OpenMP标准中关于关键的可以读到:

关键构造将关联的结构化块的执行限制为一次只能执行一个线程。

因此,两种机制都提供了处理同一问题的方法即确保代码块的互斥性。

它们是否同样优秀,还是其中之一有一些缺点?

两者都是更粗粒度的锁定机制,但是,默认情况下,OpenMP 的critical粒度更粗,因为:

所有没有名称的关键构造都被认为具有相同的未指定名称。

因此,如果未指定名称,则所有关键区域都使用相同的全局锁,这在语义上与使用相同lock_guardmutex. 尽管如此,我们可以与critical 编译指示一起指定一个名称:

可以使用可选名称来标识关键构造。

#pragma omp critical(name)
Run Code Online (Sandbox Code Playgroud)

name在 a 上指定critical与将锁传递给 语义上类似 std::lock_guard<std::mutex> lock(name);

毫无价值的是,OpenMP 还提供显式锁定机制,例如omp_lock_t (此SO 线程中的一些详细信息)。

尽管如此,只要有可能,您就应该寻求比关键区域更细粒度的同步机制,即缩减原子甚至使用数据冗余。例如,在您的代码片段中,最具性能的方法是使用该reduction子句,如下所示:

#pragma omp parallel for(+:someVar)
for (int i = 0; i < 1000; i++)
{
    ++someVar;
}
Run Code Online (Sandbox Code Playgroud)
  1. 何时使用互斥体而不是 #pragma omp critical?

在我看来,这永远不应该成为一个考虑因素,首先是因为正如迈克尔·克莱姆(Michael Klemm)指出的那样:

应该注意的一件事是:“#pragma omp critical”只能与其他“关键”结构交互。您不能将 C++ 锁和 OpenMP 锁(锁 API 或“关键”构造)与 std::mutex 等 C++ 锁混合使用。因此,您有使用 std::mutex (或顶部的 std::lock_guard)保护的代码,那么其他应该互斥的 OpenMP 代码也需要使用 std::mutex (反之亦然)。

此外,正如吉尔斯指出的那样(我也同意这一点):

原则上,混合两种不同的并行模型是一个坏主意。因此,如果您使用 OpenMP 并行性,请避免使用 C++ 并行性,因为两者之间的交互可能会出现意外。