单个构造中的原子
atomic inside a single construct
在 openMP 框架中,假设我有一系列任务应该由一个任务完成。每个任务都是不同的,所以我无法适应 #pragma omp for
结构。在单个结构中,每个任务更新一个由所有任务共享的变量。我怎样才能保护这样一个变量的更新?
一个简化的例子:
#include <vector>
struct A {
std::vector<double> x, y, z;
};
int main()
{
A r;
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i);
// DANGER
r.x = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i);
// DANGER
r.y = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i + 2);
// DANGER
r.z = std::move(res);
}
#pragma omp barrier
return 0;
}
// DANGER
下面的代码行有问题,因为它们修改了共享变量的内存内容。
在上面的示例中,它可能仍然可以正常工作,因为我正在有效地修改 r
的不同成员。问题仍然是:如何确保任务不会同时更新 r
?单个构造是否有“某种”atomic
编译指示?
each task updates a variable shared by all tasks.
实际上他们没有。考虑像这样重写代码(不需要临时向量):
void foo( std::vector<double>& x, std::vector<double>& y, std::vector<double>& z) {
#pragma omp single nowait
{
for (int i = 0; i < 10; ++i)
x.push_back(i);
}
#pragma omp single nowait
{
for (int i = 0; i < 10; ++i)
y.push_back(i * i);
}
#pragma omp single nowait
{
for (int i = 0; i < 10; ++i)
z.push_back(i * i + 2);
}
#pragma omp barrier
}
只要调用者能保证x
、y
和z
不引用同一个对象,就不存在数据竞争。代码的每一部分都修改了一个单独的向量。无需同步。
现在,这些向量来自何处并不重要。您可以这样调用函数:
A r;
foo(r.x, r.y, r.z);
PS:我对 omp 不熟悉了,我假设注释正确地做了你想让它们做的事情。
您的原始代码中没有数据竞争,因为 x
、y
和 z
是结构 A
中的不同向量(正如@已经强调的那样463035818_is_not_a_number), 因此在这方面您不必更改代码中的任何内容。
但是,您的代码中缺少 #pragma omp parallel
指令,因此目前它是一个串行程序。所以,它应该是这样的:
#pragma omp parallel num_threads(3)
{
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i);
// DANGER
r.x = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i);
// DANGER
r.y = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i + 2);
// DANGER
r.z = std::move(res);
}
}
在这种情况下,#pragma omp barrier
不是必需的,因为在平行区域的末端有一个隐含的屏障。请注意,我使用了 num_threads(3)
子句来确保只有 3 个线程分配给该并行区域。如果您跳过此子句,那么所有其他线程都将在屏障处等待。
在实际数据竞争的情况下(即不止一个 region/section 更改同一个结构成员),您可以使用 #pragma omp critical (name)
来纠正这一点。但是请记住,当临界区旁边没有足够的真正并行工作时,这种序列化可以抵消多线程的好处。
请注意,更好的解决方案是使用 #pragma omp sections
(如@PaulG 所建议)。如果 运行 并行的任务数在编译时已知 sections
是 OpenMP 中的典型选择:
#pragma omp parallel sections
{
#pragma omp section
{
//Task 1 here
}
#pragma omp section
{
//Task 2
}
#pragma omp section
{
// Task 3
}
}
郑重声明,我想证明 #pragma omp for
也很容易做到:
#pragma omp parallel for
for(int i=0;i<3;i++)
{
if (i==0)
{
// Task 1
} else if (i==1)
{
// Task 2
}
else if (i==2)
{
// Task 3
}
}
在 openMP 框架中,假设我有一系列任务应该由一个任务完成。每个任务都是不同的,所以我无法适应 #pragma omp for
结构。在单个结构中,每个任务更新一个由所有任务共享的变量。我怎样才能保护这样一个变量的更新?
一个简化的例子:
#include <vector>
struct A {
std::vector<double> x, y, z;
};
int main()
{
A r;
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i);
// DANGER
r.x = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i);
// DANGER
r.y = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i + 2);
// DANGER
r.z = std::move(res);
}
#pragma omp barrier
return 0;
}
// DANGER
下面的代码行有问题,因为它们修改了共享变量的内存内容。
在上面的示例中,它可能仍然可以正常工作,因为我正在有效地修改 r
的不同成员。问题仍然是:如何确保任务不会同时更新 r
?单个构造是否有“某种”atomic
编译指示?
each task updates a variable shared by all tasks.
实际上他们没有。考虑像这样重写代码(不需要临时向量):
void foo( std::vector<double>& x, std::vector<double>& y, std::vector<double>& z) {
#pragma omp single nowait
{
for (int i = 0; i < 10; ++i)
x.push_back(i);
}
#pragma omp single nowait
{
for (int i = 0; i < 10; ++i)
y.push_back(i * i);
}
#pragma omp single nowait
{
for (int i = 0; i < 10; ++i)
z.push_back(i * i + 2);
}
#pragma omp barrier
}
只要调用者能保证x
、y
和z
不引用同一个对象,就不存在数据竞争。代码的每一部分都修改了一个单独的向量。无需同步。
现在,这些向量来自何处并不重要。您可以这样调用函数:
A r;
foo(r.x, r.y, r.z);
PS:我对 omp 不熟悉了,我假设注释正确地做了你想让它们做的事情。
您的原始代码中没有数据竞争,因为 x
、y
和 z
是结构 A
中的不同向量(正如@已经强调的那样463035818_is_not_a_number), 因此在这方面您不必更改代码中的任何内容。
但是,您的代码中缺少 #pragma omp parallel
指令,因此目前它是一个串行程序。所以,它应该是这样的:
#pragma omp parallel num_threads(3)
{
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i);
// DANGER
r.x = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i);
// DANGER
r.y = std::move(res);
}
#pragma omp single nowait
{
std::vector<double> res;
for (int i = 0; i < 10; ++i)
res.push_back(i * i + 2);
// DANGER
r.z = std::move(res);
}
}
在这种情况下,#pragma omp barrier
不是必需的,因为在平行区域的末端有一个隐含的屏障。请注意,我使用了 num_threads(3)
子句来确保只有 3 个线程分配给该并行区域。如果您跳过此子句,那么所有其他线程都将在屏障处等待。
在实际数据竞争的情况下(即不止一个 region/section 更改同一个结构成员),您可以使用 #pragma omp critical (name)
来纠正这一点。但是请记住,当临界区旁边没有足够的真正并行工作时,这种序列化可以抵消多线程的好处。
请注意,更好的解决方案是使用 #pragma omp sections
(如@PaulG 所建议)。如果 运行 并行的任务数在编译时已知 sections
是 OpenMP 中的典型选择:
#pragma omp parallel sections
{
#pragma omp section
{
//Task 1 here
}
#pragma omp section
{
//Task 2
}
#pragma omp section
{
// Task 3
}
}
郑重声明,我想证明 #pragma omp for
也很容易做到:
#pragma omp parallel for
for(int i=0;i<3;i++)
{
if (i==0)
{
// Task 1
} else if (i==1)
{
// Task 2
}
else if (i==2)
{
// Task 3
}
}