openmp是由一系列#paragma指令组成,这些指令控制如何多线程的执行程序。另外,即使编译器不支持omp,程序也也能够正常运行,只是程序不会多线程并行运行。以下为使用omp的简单的例子:
int main()
{
vector vecInt(100);
#pragma omp parallel for
for (int i = 0; i < vecInt.size(); ++i)
{
vecInt[i] = i*i;
}
return 0;
}
以上代码会自动以多线程的方式运行for循环中的内容。如果你删除"#pragma omp parallel for"这行,程序依然能够正常运行,唯一的区别在于程序是在单线程中执行。由于C和C++的标准规定,当编译器遇到无法识别的"#pragma"指令时,编译器自动忽略这条指令。所以即使编译器不支持omp,也不会影响程序的编译和运行。
parallel编译指示
#pragma omp parallel
{
cout << "parallel run!!!\n";
}
线程之间共享的变量是通过传递引用或者利用register变量来实现同步的,其中register变量在代码执行结束之后或者在flush指令调用时进行同步。
我们也可以利用if条件判断来决定是否对后续的代码采用并行方式执行,如:
externint parallelism_enabled;
#pragma omp parallel for if(parallelism_enabled)
for(int c=0; c
for指令
#pragma omp parallel for
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
以上的代码执行后,会打印出[0,9]这10个数字。但是它们的顺序是随机出现的,在我的电脑上,运行的输出是"0 1 2 8 9 6 7 3 4 5"。事实上,输出结果不会是完全随机的,输出的序列是局部有序的,因为在编译器对for循环的拆分相当于下面的代码:
int this_thread = omp_get_thread_num();
int num_threads = omp_get_num_threads();
int my_start = (this_thread)* 10 / num_threads;
int my_end = (this_thread + 1) * 10 / num_threads;
for (int n = my_start; n < my_end; ++n)
printf("%d ", n);
#pragma omp parallel
{
#pragma omp for
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
}
#pragma omp parallel for schedule(dynamic)
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
那么打印的结果则有可能不是局部有序的。
#pragma omp parallel for schedule(dynamic,3)
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
在这个例子中,每个线程每次都索取3个迭代值。执行完之后,再拿3个迭代值,直到for循环所有迭代值都运行结束。在最后一次索取的结果有可能不足3个。在程序内部,上面的例子与下面的代码是等价的:
int a,b;
if(GOMP_loop_dynamic_start(0,10,1,3,&a,&b))
{
do{
for(int n=a; n
#pragma omp parallel for ordered schedule(dynamic)
for (int i = 0; i < 10; ++i)
{
Data data = ReadFile(files[i]);
#pragma omp ordered
PutDataToDataset(data);
}
这个循环负责读取10个文件,然后将数据放入一个内存结构中。读文件的操作是并行的,但是将数据存入内存结构中则是严格串行的。即先存第一个文件数据,然后第二个...,最后是第十个文件。假设一个线程已经读取了第七个文件的,但是第六个文件还没有存入内存结构,那么这个线程会阻塞,知道第六个文件存入内存结构之后,线程才会继续运行。
#pragma omp parallel sections
{
{ Work1();}
#pragma omp section
{ Work2();
Work3();}
#pragma omp section
{ Work4();}
}
Work1
,
Work2 + Work3
以及
Work4可以并行运行,但是work2和work3的运行必须是串行运行,并且每个work都只会被运行一次。
task
指令(OpenMP 3.0新增)struct node { node *left,*right;};
externvoid process(node*);
void traverse(node* p)
{
if(p->left)
#pragma omp task // p is firstprivate by default
traverse(p->left);
if(p->right)
#pragma omp task // p is firstprivate by default
traverse(p->right);
process(p);
}
struct node { node *left,*right;};
externvoid process(node*);
void postorder_traverse(node* p)
{
if(p->left)
#pragma omp task // p is firstprivate by default
postorder_traverse(p->left);
if(p->right)
#pragma omp task // p is firstprivate by default
postorder_traverse(p->right);
#pragma omp taskwait
process(p);
}
struct node {int data; node* next;};
externvoid process(node*);
void increment_list_items(node* head)
{
#pragma omp parallel
{
#pragma omp single
{
for(node* p = head; p; p = p->next)
{
#pragma omp task
process(p);// p is firstprivate by default
}
}
}
}
#pragma omp atomic
counter += value;
critical
指令#pragma omp critical(dataupdate)
{
datastructure.reorganize();
}
...
#pragma omp critical(dataupdate)
{
datastructure.reorganize_again();
}
omp_init_lock
初始化锁,初始化后锁处于未锁定状态.omp_destroy_lock
销毁锁,调用这个函数时,锁必须是未锁定状态.omp_set_lock
尝试获取锁,如果锁已经被其他线程加锁了,那当前线程进入阻塞状态。omp_unset_lock
释放锁,调用这个方法的线程必须已经获得了锁,如果当前线程没有获得锁,则会有未定义行为。omp_test_lock
a尝试获取锁,获取锁成功则返回1,否则返回0.flush
指令例如,
/* presumption: int a = 0, b = 0; */
/* First thread */ /* Second thread */
b =1; a =1;
#pragma omp flush(a,b) #pragma omp flush(a,b)
if(a ==0)if(b ==0)
{{
/* Critical section */ /* Critical section */
}}
private
, firstprivate,lastprivate
及 shared指令控制变量共享方式
这些指令用于控制变量在线程组中多个线程之间的共享方式。其中private,firstprivate,lastprivate表示变量的共享方式是私有的,即每个线程都有一份自己的拷贝;而shared表示线程组的线程访问的是同一个变量。
私有变量共享方式有三种指令,它们的区别在于:
private:每个线程都有一份自己的拷贝,但是这些变量并没有拷贝值,即如果变量是int,long,double等这些内置类型,那么这些变量在进入线程时时未初始化状态的;如果变量是类的实例对象,那么在线程中变量是通过默认构造得到的对象,假设类没有默认构造,则编译会报错,告诉你类没有可用的默认构造;
firstPrivate:每个线程有一份自己的拷贝,每个线程都会通过复制一份。如果变量是int,long,double等内置类型则直接复制,如果为类的实例对象,则会调用示例对象的拷贝构造函数,这就意味着,假如类是的拷贝构造不可访问,则变量不能够使用firstprivate方式共享;
lastprivate:变量在每个线程的共享方式与private一致,但不同的是,变量的最后一次迭代中的值会flush会主线程中的变量中。最后一次迭代的意思是,如果是for循环,则主线程的变量的值是最后一个迭代值那次迭代中赋的值;如果是section,则主线程的变量最终的值是最后一个section中赋的值。要注意的是,最终主线程的中变量的值并非通过拷贝构造赋值的,而是通过operator=操作符,所以如果类的赋值操作符不可访问,那么变量不能采用lastprivate方式共享。
default
指令int a, b=0;
#pragma omp parallel default(none) shared(b)
{
b += a;
}
reduction
指令Operator | Initialization value |
---|---|
+ , - , | , ^ , || |
0 |
* , && |
1 |
& |
~0 |
int factorial(int number)
{
int fac =1;
#pragma omp parallel for reduction(*:fac)
for(int n=2; n<=number;++n)
fac *= n;
return fac;
}
如果这里我们不用reduction,那么则需用适用atomic指令,代码如下:
int factorial(int number)
{
int fac =1;
#pragma omp parallel for
for(int n=2; n<=number;++n)
{
#pragma omp atomic
fac *= n;
}
return fac;
}
使用reduction指令的代码事实上类似于以下代码:
int factorial(int number)
{
int fac =1;
#pragma omp parallel
{
int fac_private =1;
#pragma omp for nowait
for(int n=2; n<=number;++n)
fac_private *= n;
#pragma omp atomic
fac *= fac_private;
}
return fac;
}
barrier和
nowait指令
#pragma omp parallel
{
/* All threads execute this. */
SomeCode();
#pragma omp barrier
/* All threads execute this, but not before
* all threads have finished executing SomeCode().
*/
SomeMoreCode();
}
#pragma omp parallel
{
#pragma omp for
for(int n=0; n<10;++n) Work();
// This line is not reached before the for-loop is completely finished
SomeMoreCode();
}
// This line is reached only after all threads from
// the previous parallel block are finished.
CodeContinues();
#pragma omp parallel
{
#pragma omp for nowait
for(int n=0; n<10;++n) Work();
// This line may be reached while some threads are still executing the for-loop.
SomeMoreCode();
}
// This line is reached only after all threads from
// the previous parallel block are finished.
CodeContinues();
single
和 master
指令#pragma omp parallel
{
Work1();
#pragma omp single
{
Work2();
}
Work3();
}
master指令则指定其相关的代码块必须在主线程中执行,且其它线程不必在代码块后阻塞。
#pragma omp parallel
{
Work1();
// This...
#pragma omp master
{
Work2();
}
// ...is practically identical to this:
if(omp_get_thread_num()==0)
{
Work2();
}
Work3();
}
#pragma omp parallel for
for(int y=0; y<25;++y)
{
#pragma omp parallel for
for(int x=0; x<80;++x)
{
tick(x,y);
}
}
#pragma omp parallel for
for(int y=0; y<25;++y)
{
#pragma omp for // ERROR, nesting like this is not allowed.
for(int x=0; x<80;++x)
{
tick(x,y);
}
}
#pragma omp parallel for collapse(2)
for(int y=0; y<25;++y)
for(int x=0; x<80;++x)
{
tick(x,y);
}
在OpenMP 2.5中,我们可以通过将多层循环改为单层循环的方法来达到目的,这样便无需循环嵌套:
#pragma omp parallel for
for(int pos=0; pos<(25*80);++pos)
{
int x = pos%80;
int y = pos/80;
tick(x,y);
}
omp_set_nested(1);
#pragma omp parallel for
for(int y=0; y<25;++y)
{
#pragma omp parallel for
for(int x=0; x<80;++x)
{
tick(x,y);
}
}
for(int y=0; y<25;++y)
{
#pragma omp parallel for
for(int x=0; x<80;++x)
{
tick(x,y);
}
}
constchar* FindAnyNeedle(constchar* haystack, size_t size,char needle)
{
for(size_t p =0; p < size;++p)
if(haystack[p]== needle)
{return haystack+p; //找到needle,直接退出函数
}
return NULL;
}
以下为使用布尔标记来通知其他线程的示例:
constchar* FindAnyNeedle(constchar* haystack, size_t size,char needle)
{
constchar* result = NULL;
bool done =false;
#pragma omp parallel for
for(size_t p =0; p < size;++p)
{
#pragma omp flush(done)
if(!done)
{
/* Do work only if no thread has found the needle yet. */
if(haystack[p]== needle)
{
/* Inform the other threads that we found the needle. */
done =true;
#pragma omp flush(done)
result = haystack+p;
}
}
}
return result;
}
constchar* FindAnyNeedle(constchar* haystack, size_t size,char needle)
{
constchar* result = NULL;
#pragma omp parallel for
for(size_t p =0; p < size;++p)
if(haystack[p]== needle)
result = haystack+p;
return result;
}
事实上,omp针对这个问题并没有很好的解决办法,如果确实需要,那只能求助于其他线程库了。