简单的基于任务的 OpenMP 应用程序挂起
Simple task-based OpenMP application hangs
下面的小程序(online version)试图计算一个64×64的正方形的面积,通过递归地分成四个正方形,直到最小的正方形有单位长度(几乎不是最优的)。但由于某种原因,程序挂起。哪里做错了?
#include <iostream>
unsigned compute( unsigned length )
{
if( length == 1 ) return length * length;
unsigned a[4] , area = 0 , len = length/2;
for( unsigned i = 0; i < 4; ++i )
{
#pragma omp task
{
a[i] = compute( len );
}
#pragma omp single
{
area += a[i];
}
}
return area;
}
int main()
{
unsigned area , length = 64;
#pragma omp parallel
{
area = compute( length );
}
std::cout << area << std::endl;
}
single
构造充当团队中所有线程的隐式屏障。然而,并非团队中的所有线程都会遇到这个单块,因为不同的线程在不同的递归深度下工作。这就是您的应用程序挂起的原因。
无论如何你的代码都不正确。在你的任务块之后,a[i]
还没有分配,所以你不能立即使用它!您必须等待任务完成。当然你不应该在循环内这样做,否则任务分配不会利用任何并行性。解决方案是在循环结束时执行此操作。您还必须将 a
指定为共享才能使输出可见:
for( unsigned i = 0; i < 4; ++i )
{
#pragma omp task shared(a)
{
a[i] = compute( len );
}
}
#pragma omp taskwait
for( unsigned i = 0; i < 4; ++i )
{
area += a[i];
}
请注意,缩减没有包含 single
结构!计算由任务执行,因此只有一个线程应该拥有自己的本地 area
。但是,在您第一次生成任何任务之前,您需要一个 single
结构:
#pragma omp parallel
#pragma omp single
{
area = compute( length );
}
简单来说,这会打开一个并行区域,其中包含一组线程,并且只有一个线程开始初始计算。其他线程将使用 task
构造接手此初始线程稍后生成的任务。这就是任务分配的意义所在。
受关于 taskwait 以及如何避免它的讨论的启发,我在下面展示了原始代码的一个稍微修改过的版本。请注意,在这种情况下,单个构造末尾的隐含屏障确实是必要的。
unsigned tp_area = 0;
#pragma omp threadprivate(tp_area)
void compute (unsigned length)
{
if (length == 1)
{
tp_area += 1;
return;
}
unsigned len = length / 2;
for (unsigned i = 0; i < 4; ++i)
{
#pragma omp task
{
compute (len);
}
}
}
int main ()
{
unsigned area, length = 64;
#pragma omp parallel
{
#pragma omp single
{
compute (length);
}
#pragma omp atomic
area += tp_area;
}
std::cout << area << std::endl;
}
下面的小程序(online version)试图计算一个64×64的正方形的面积,通过递归地分成四个正方形,直到最小的正方形有单位长度(几乎不是最优的)。但由于某种原因,程序挂起。哪里做错了?
#include <iostream>
unsigned compute( unsigned length )
{
if( length == 1 ) return length * length;
unsigned a[4] , area = 0 , len = length/2;
for( unsigned i = 0; i < 4; ++i )
{
#pragma omp task
{
a[i] = compute( len );
}
#pragma omp single
{
area += a[i];
}
}
return area;
}
int main()
{
unsigned area , length = 64;
#pragma omp parallel
{
area = compute( length );
}
std::cout << area << std::endl;
}
single
构造充当团队中所有线程的隐式屏障。然而,并非团队中的所有线程都会遇到这个单块,因为不同的线程在不同的递归深度下工作。这就是您的应用程序挂起的原因。
无论如何你的代码都不正确。在你的任务块之后,a[i]
还没有分配,所以你不能立即使用它!您必须等待任务完成。当然你不应该在循环内这样做,否则任务分配不会利用任何并行性。解决方案是在循环结束时执行此操作。您还必须将 a
指定为共享才能使输出可见:
for( unsigned i = 0; i < 4; ++i )
{
#pragma omp task shared(a)
{
a[i] = compute( len );
}
}
#pragma omp taskwait
for( unsigned i = 0; i < 4; ++i )
{
area += a[i];
}
请注意,缩减没有包含 single
结构!计算由任务执行,因此只有一个线程应该拥有自己的本地 area
。但是,在您第一次生成任何任务之前,您需要一个 single
结构:
#pragma omp parallel
#pragma omp single
{
area = compute( length );
}
简单来说,这会打开一个并行区域,其中包含一组线程,并且只有一个线程开始初始计算。其他线程将使用 task
构造接手此初始线程稍后生成的任务。这就是任务分配的意义所在。
受关于 taskwait 以及如何避免它的讨论的启发,我在下面展示了原始代码的一个稍微修改过的版本。请注意,在这种情况下,单个构造末尾的隐含屏障确实是必要的。
unsigned tp_area = 0;
#pragma omp threadprivate(tp_area)
void compute (unsigned length)
{
if (length == 1)
{
tp_area += 1;
return;
}
unsigned len = length / 2;
for (unsigned i = 0; i < 4; ++i)
{
#pragma omp task
{
compute (len);
}
}
}
int main ()
{
unsigned area, length = 64;
#pragma omp parallel
{
#pragma omp single
{
compute (length);
}
#pragma omp atomic
area += tp_area;
}
std::cout << area << std::endl;
}