jri*_*jri 6 c++ performance multithreading mutex openmp
让我们考虑一些代码,以便在具有多个线程的for循环中安全地递增变量。
为此,您必须在增加变量时使用某种锁定机制。当我在寻找解决方案时,我想出了以下解决方案。
我的问题是:
mutex而不是#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)
从cppreference.com关于lock_guard可以读到
lock_guard 类是一个互斥锁包装器,它提供了一种方便的 RAII 式机制,用于在作用域块的持续时间内拥有互斥锁。
从OpenMP标准中关于关键的可以读到:
关键构造将关联的结构化块的执行限制为一次只能执行一个线程。
因此,两种机制都提供了处理同一问题的方法,即确保代码块的互斥性。
它们是否同样优秀,还是其中之一有一些缺点?
两者都是更粗粒度的锁定机制,但是,默认情况下,OpenMP 的critical粒度更粗,因为:
所有没有名称的关键构造都被认为具有相同的未指定名称。
因此,如果未指定名称,则所有关键区域都使用相同的全局锁,这在语义上与使用相同lock_guard的mutex. 尽管如此,我们可以与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)
- 何时使用互斥体而不是 #pragma omp critical?
在我看来,这永远不应该成为一个考虑因素,首先是因为正如迈克尔·克莱姆(Michael Klemm)指出的那样:
应该注意的一件事是:“#pragma omp critical”只能与其他“关键”结构交互。您不能将 C++ 锁和 OpenMP 锁(锁 API 或“关键”构造)与 std::mutex 等 C++ 锁混合使用。因此,您有使用 std::mutex (或顶部的 std::lock_guard)保护的代码,那么其他应该互斥的 OpenMP 代码也需要使用 std::mutex (反之亦然)。
此外,正如吉尔斯指出的那样(我也同意这一点):
原则上,混合两种不同的并行模型是一个坏主意。因此,如果您使用 OpenMP 并行性,请避免使用 C++ 并行性,因为两者之间的交互可能会出现意外。
| 归档时间: |
|
| 查看次数: |
243 次 |
| 最近记录: |