在C++中使⽤openmp进⾏多线程编程
在C++中使⽤openmp进⾏多线程编程
⼀、前⾔
多线程在实际的编程中的重要性不⾔⽽喻。对于C++⽽⾔,当我们需要使⽤多线程时,可以使⽤boost::thread库或者⾃从C++ 11开始⽀持的std::thread,也可以使⽤操作系统相关的线程API,如在Linux上,可以使⽤pthread库。除此之外,还可以使⽤omp来使⽤多线程。它的好处是跨平台,使⽤简单。
在Linux平台上,如果需要使⽤omp,只需在编译时使⽤"-fopenmp"指令。在Windows的visual studio开发环境中,开启omp⽀持的步骤
为“项⽬属性 -> C/C++ -> 所有选项 -> openmp⽀持 -> 是(/openmp)”。
本⽂我们就介绍omp在C++中的使⽤⽅法。
⼆、c++ openmp⼊门简介
openmp是由⼀系列#paragma指令组成,这些指令控制如何多线程的执⾏程序。另外,即使编译器不⽀持omp,程序也也能够正常运⾏,只是程序不会多线程并⾏运⾏。以下为使⽤omp的简单的例⼦:
int main()
{
vector<int> vecInt(100);
#pragma omp parallel for
for (int i = 0; i < vecInt.size(); ++i)
{
vecInt[i] = i*i;
}
return 0;
}
12345678910
以上代码会⾃动以多线程的⽅式运⾏for循环中的内容。如果你删除"#pragma omp parallel for"这⾏,程序依然能够正常运⾏,唯⼀的区别在于程序是在单线程中执⾏。由于C和C++的标准规定,当编译器遇到⽆法识别的"#pragma"指令时,编译器⾃动忽略这条指令。所以即使编译器不⽀持omp,也不会影响程序的编译和运⾏。
三、omp语法
所有的omp指令都是以"#pragma omp“开头,换⾏符结束。并且除了barrier和flush两个指令不作⽤于代码以外,其他的指令都只与指令后⾯的那段代码相关,⽐如上⾯例⼦中的for循环。
private私有变量
private ⼦句可以将变量声明为线程私有,声明称线程私有变量以后,每个线程都有⼀个该变量的副本,线程之间不会互相影响,其他线程⽆法访问其他线程的副本。原变量在并⾏部分不起任何作⽤,也不会受到并⾏部分内部操作的影响。
int temp;
#pragma omp parallel for private(temp)
for (int i = 0; i < 100; i++) {
temp = array[i];
array[i] = doSomething(temp);
}
firstprivate
private⼦句不能继承原变量的值,但是有时我们需要线程私有变量继承原来变量的值,这样我们就可以使⽤firstprivate⼦句来实现
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel for firstprivate(t)
for (i = 0; i < 5; i++)
{
//次数t被初始化为20
t += i;
printf("t = %d\n", t);
}
//此时t=20
printf("outside t = %d\n", t);
return 0;
}
lastprivate
lastprivate⼦句在退出并⾏部分时将计算结果赋值回原变量
lastprivate必须要搭配firstprivate⼀起使⽤
需要注意的是,在循环迭代中,是最后⼀次迭代的值赋值给原变量;如果是section结构,那么是程序语法上的最后⼀个section语句赋值给原变量。
如果是类(class)变量作为lastprivate的参数时,我们需要⼀个缺省构造函数,除⾮该变量也作为firstprivate⼦句的参数;此外还需要⼀个拷贝赋值操作符。
int main(int argc, char* argv[])
{
int t = 20, i;
// YOUR CODE HERE
#pragma omp parallel for firstprivate(t), lastprivate(t)
// END OF YOUR CODE
for (i = 0; i < 5; i++)
{
t += i;
printf("t = %d\n", t);
}
printf("outside t = %d\n", t);
return 0;
}
===== OUTPUT =====
t = 20
t = 24
t = 23
t = 21
t = 22
outside t = 24
shared
指定⼀个或多个变量为多个线程间的共享变量
reductions
⼀种常见的循环就是累加变量,OpenMP 有专门的语句reduction
OpenMP 为每个线程提供了私有的sum变量,当线程退出时,OpenMP 再把每个线程的部分和加在⼀起得到最终结果。
reduction⽀持“+,-,*,&,|,&&,||”
int sum = 0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < 100; i++) {
sum += array[i];
}
critical
critical区域同⼀时间只能被⼀条线程执⾏
#include <iostream>
int main() {
int max = 0;
int a[10] = {11, 2, 33, 49, 113, 20, 321, 250, 689, 16};
#pragma omp parallel for
for (int i=0;i<10;i++) {
int temp = a[i];
#pragma omp critical
{
if (temp > max)
max = temp;
}
}
}
std::cout<<"max: "<<max<<std::endl;
return 0;
}
四、parallel编译指⽰
parallel告诉编译器开始⼀个并⾏块,编译器会创建⼀个包含N(在运⾏时决定,通常为服务器的逻辑核数,在Linux上查看机器的逻辑核数
命令为:cat /proc/cpuinfo| grep "processor"| wc -l)个线程的线程组,所有线程都运⾏接下来的语句或者由”{…}"包含的代码块,在这执⾏
结束之后,⼜回到主线程,创建的这N个线程会被回收。
#pragma omp parallel
{
cout << "parallel run\n";
}
1234
以上代码在逻辑核数为4的cpu的电脑上运⾏时,输出了4⾏”parallel run"。即编译器创建了⼀个包含4
个线程的线程组来运⾏这段代码。在
这段代码运⾏结束后,程序执⾏回到主线程。GCC编译器的实现⽅式是在内部创建⼀个函数,然后将相关的执⾏代码移⾄这个函数,这样
⼀来代码块中定义的变量成为了线程的局部变量,互不影响。⽽ICC的实现⽅式是利⽤fork()来实现。
线程之间共享的变量是通过传递引⽤或者利⽤register变量来实现同步的,其中register变量在代码执⾏结束之后或者在flush指令调⽤时进⾏
同步。
我们也可以利⽤if条件判断来决定是否对后续的代码采⽤并⾏⽅式执⾏,如:
externint parallelism_enabled;
#pragma omp parallel for if(parallelism_enabled)
for(int c=0; c<n;++c)
handle(c);
1234
在这个例⼦中,如果parallelism_enabled为false,那么这个for循环只会由⼀个线程来执⾏。
五、for指令
omp中的for指令⽤于告诉编译器,拆分接下来的for循环,并分别在不同的线程中运⾏不同的部分。如果for指令后没有紧接着for循环,编译
器会报错。例如,
#pragma omp parallel for
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
12345
以上的代码执⾏后,会打印出[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);
123456
以上代码中,omp_get_thread_num()⽤于获取当前线程在当前线程组中的序号;omp_get_num_thre
ads()⽤于获取线程组中的线程数。所
以线程组中每个线程都运⾏了for循环中的不同部分。
这⾥提到for循环的不同部分在线程组中的不同线程中执⾏的,线程组是在程序遇到"#pragma omp parallel"时创建,在程序块(parallel后
的”{…}"或者语句)结束后,线程组中的只有⼀个主线程。因此上⾯⽰例中的代码事实上是以下代码的缩写:
#pragma omp parallel
{
#pragma omp for
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
schedule用法及搭配}
12345678
此处的"#pragma omp for"即使在“#pragma omp parallel”指令创建的线程组中执⾏的。加⼊此处没有#pragma omp parallel指令,那么for循
环只会在主线程中执⾏。parallel指令所创建的线程组的线程数默认是有编译器决定的,我们也可以通过num_threads指令来指定线程数,
如#pragma omp parallel num_threads(3)即告诉编译器,此处需要创建⼀个包含3个线程的线程组。
六、Schedule指令
Schedule指令提供对for指令中线程调度更多的控制能⼒。它有两种调度⽅式:static和dynamic。
static:每个线程⾃⾏决定要执⾏哪个块,即每个线程执⾏for循环中的⼀个⼦块。
dynamic:⼀个线程并不是执⾏for循环的⼀个⼦块,⽽是每次都向omp运⾏时库索取⼀个for循环中的
迭代值,然后执⾏这次迭代,在执⾏
完之后再索取新的值。因此,线程有可能执⾏任意的迭代值,⽽不是⼀个⼦块。
之前的”#pragma omp parallel for“实际上的效果是”#pragma omp parallel for schedule(static)"。如果我们将之前的⽰例采⽤dynamic调度⽅
式,即:
#pragma omp parallel for schedule(dynamic)
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
12345
那么打印的结果则有可能不是局部有序的。
在dynamic调度⽅式中,还可以指定每次索取的迭代值数量。如
#pragma omp parallel for schedule(dynamic,3)
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
12345
在这个例⼦中,每个线程每次都索取3个迭代值。执⾏完之后,再拿3个迭代值,直到for循环所有迭代值都运⾏结束。在最后⼀次索取的结
果有可能不⾜3个。在程序内部,上⾯的例⼦与下⾯的代码是等价的:
int a,b;if(GOMP_loop_dynamic_start(0,10,1,3,&a,&b)){do{for(int n=a; n<b;++n) printf(" %d", n);}while(GOMP_loop_dynamic_next(&a,&b));}1234567
七、ordered指令
ordered指令⽤于控制⼀段代码在for循环中的执⾏顺序,它保证这段代码⼀定是按照for中的顺序依次执⾏的。
#pragma omp parallel for ordered schedule(dynamic)for (int i = 0; i < 10; ++i){ Data data = ReadFile(files[i]);#pragma omp ordered PutDataToDataset(data);}123456
这个循环负责读取10个⽂件,然后将数据放⼊⼀个内存结构中。读⽂件的操作是并⾏的,但是将数据存⼊内存结构中则是严格串⾏的。即
先存第⼀个⽂件数据,然后第⼆个…,最后是第⼗个⽂件。假设⼀个线程已经读取了第七个⽂件的,但是第六个⽂件还没有存⼊内存结
构,那么这个线程会阻塞,知道第六个⽂件存⼊内存结构之后,线程才会继续运⾏。
在每⼀个ordered for循环中,有且仅有⼀个“#pragma omp ordered"指令限定的代码块。
⼋、sections指令
section指令⽤于指定哪些程序块可以并⾏运⾏。⼀个section块内的代码必须串⾏运⾏,⽽section块之间是可以并⾏运⾏的。如,
#pragma omp parallel sections{{ Work1();}#pragma omp section{ Work2(); Work3();}#pragma omp section{ Work4();}}123456789
以上代码表明,Work1, Work2 + Work3 以及 Work4可以并⾏运⾏,但是work2和work3的运⾏必须是串⾏运⾏,并且每个work都只会被运
⾏⼀次。
九、task 指令(OpenMP 3.0新增)
当觉得for和section指令⽤着不⽅便时,可以⽤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 firstpri
vate by default travers 上⾯的⽰例中,当处理当前节点process§时,并不能够保证p的左右⼦树已经处理完毕。为了保证在处理当前节点前,当前节点的左右⼦树
已经处理完成,可以使⽤taskwait指令,这个指令会保证前⾯的task都已经处理完成,然后才会继续往下⾛,添加taskwait指令之后,以上
⽰例代码变为:
⽰例代码变为:
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 以下⽰例演⽰了如何利⽤task指令来并⾏处理链表的元素,由于指针p默认是firstprivate⽅式共享,所以⽆需特别指定。
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 f ⼗、atomic指令
atomic指令⽤于保证其后续的语句执⾏时原⼦性的。所谓原⼦性,即事务的概念,它的执⾏不可拆分,
要么执⾏成功,要么什么都没有执
⾏。例如,
#pragma omp atomic counter += value;12
以上代码中,atomic保证对counter的改变时原⼦性的,如果多个线程同时执⾏这句代码,也能够保证counter最终拥有正确的值。
需要说明的是,atomic只能⽤于简单的表达式,⽐如+=、-=、*=、&=等,它们通常能够被编译成⼀条指令。如果上⾯的⽰例改为"counter = counter + value",那将⽆法通过编译;atomic作⽤的表达式中也不能够有函数调⽤、数组索引等操作。另外,它只保证等号左边变量的赋
值操作的原⼦性,等号右边的变量的取值并不是原⼦性的。这就意味着另外⼀个线程可能在赋值前改变等号右边的变量。如果要保证更复
杂的原⼦性可以参考后续的critical指令。
⼗⼀、critical 指令
critical指令⽤于保证其相关联的代码只在⼀个线程中执⾏。另外,我们还可以给critical指令传递⼀个名称,这个名称是全局性的,所有具有
相同名字的critical相关联的代码保证不会同时在多个线程中运⾏,同⼀时间最多只会有⼀个代码块在运⾏。如果没有指定名称,那系统会
给定⼀个默认的名称:
#pragma omp critical(dataupdate){ anize();}...#pragma omp critical(dataupdate){ anize_again();}123456789
以上代码展⽰的两个名称为"dataupdate"的critical代码⼀次只会有⼀个执⾏,即datastructure的reorganize()和reorganize_again()不会并⾏
运⾏,⼀次最多只会有⼀个在线程中执⾏。
⼗⼆、openmp中的锁
omp运⾏库提供了⼀种锁:omp_lock_t,它定义在omp.h头⽂件中。针对omp_lock_t有5中操作,它们分别是:
omp_init_lock 初始化锁,初始化后锁处于未锁定状态.
omp_destroy_lock 销毁锁,调⽤这个函数时,锁必须是未锁定状态.
omp_set_lock 尝试获取锁,如果锁已经被其他线程加锁了,那当前线程进⼊阻塞状态。
omp_unset_lock 释放锁,调⽤这个⽅法的线程必须已经获得了锁,如果当前线程没有获得锁,则会有未定义⾏为。
omp_test_lock a尝试获取锁,获取锁成功则返回1,否则返回0.
omp_lock_t相当于mutex,如果线程已经获得了锁,那在释放锁之前,当前线程不能对锁进⾏上锁。为了满⾜这种递归锁的需求,omp提供
了omp_nest_lock_t,这种锁相当于recursive_mutex可以递归上锁,但是释放操作必须与上锁操作⼀⼀对应,否则锁不会得到释放。
⼗三、flush 指令
对于多线程之间共享的变量,编译器有可能会将它们设为寄存器变量,意味着每个线程事实上都只是拥有这个变量的副本,导致变量值并
没有在多个线程之间共享。为了保证共享变量能够在线程之间是真实共享,保证每个线程看到的值都是⼀致的,可以使⽤flush指令告诉编
译器我们需要哪些哪些共享变量。当我们要在多个线程中读写共同的变量时,我们都应该使⽤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 sectio 在上⾯的例⼦中,变量a,b在两个线程中是共享的,两个线程任何时候看到的a,b的值都是⼀致的,即线程1所见的即使线程2所见的。
⼗四、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 指令
default命令⽤于设置所有变量的默认的共享⽅式,如default(shared)表⽰所有变量默认共享⽅式为shared。除此之外,我们可以使⽤
default(none)来检查我们是否显⽰设置了所有使⽤了的变量的共享⽅式,如:
int a, b=0;#pragma omp parallel default(none) shared(b){ b += a;}12345
以上代码⽆法通过编译,因为在parallel的代码块中使⽤了变量a和b,但是我们只设置了b的共享⽅式,⽽没有设置变量a的共享⽅式。
另外需要注意的是,default中的参数不能使⽤private、firstprivate以及lastprivate。
⼗六、reduction 指令
reductino指令是private,shared及atomic的综合体。它的语法是:
reduction(operator : list)
其中operator指操作符,list表⽰操作符要作⽤的列表,通常是⼀个共享变量名,之所以称之为列表是因为线程组中的每个线程都有⼀份变
量的拷贝,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;}12345678
开始,每个线程会拷贝⼀份fac;
parallel块结束之后,每个线程中的fac会利⽤“*”进⾏聚合,并将聚合的结果设置回主线程中的fac中。
如果这⾥我们不⽤reduction,那么则需⽤适⽤atomic指令,代码如下:
int factorial(int number){int fac =1;#pragma omp parallel forfor(int n=2; n<=number;++n){#pragma omp atomic fac *= n;}return fac;}1234567891011
但是这样⼀来,性能会⼤⼤的下降,因为这⾥没有使⽤局部变量,每个线程对fac的操作都需要进⾏同步。所以在这个例⼦中,并不会从多
线程中受益多少,因为atomic成为了性能瓶颈。
使⽤reduction指令的代码事实上类似于以下代码:
int factorial(int number){int fac =1;#pragma omp parallel{int fac_private =1;#pragma omp for nowaitfor(int n=2; n<=number;++n) fac_private *= n;#pragma omp atomic fac *= fac_private;}return fac;}123456789101112131注:最后的聚合实际是包括主线程中共享变量的初始值⼀起的,在阶乘的例⼦中,如果fac的初始值不是1,⽽是10,则最终的结果会是实
际阶乘值的10倍!
⼗七、barrier和nowait指令
barrier指令是线程组中线程的⼀个同步点,只有线程组中的所有线程都到达这个位置之后,才会继续往下运⾏。⽽在每个for、section以及
后⾯要讲到的single代码块最后都隐式的设置了barrier指令。例如
#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();}12345678910 nowait指令⽤来告诉编译器⽆需隐式调⽤barrier指令,因此如果为for、section、single设置了nowait标志,则在它们最后不会隐式的调⽤
barrier指令,例如:
#pragma omp parallel{#pragma omp forfor(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 paral ⼗⼋、single 和 master 指令
#pragma omp parallel{#pragma omp forfor(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 paral ⼗⼋、single 和 master 指令
single指令相关的代码块只运⾏⼀个线程执⾏,但并不限定具体哪⼀个线程来执⾏,其它线程必须跳过这个代码块,并在代码块后wait,直
到执⾏这段代码的线程完成。
#pragma omp parallel{ Work1();#pragma omp single{ Work2();} Work3();}123456789
以上代码中,work1()和work3()会在线程组中所有线程都运⾏⼀遍,但是work2()只会在⼀个线程中执⾏,即只会执⾏⼀遍。
master指令则指定其相关的代码块必须在主线程中执⾏,且其它线程不必在代码块后阻塞。
#pragma omp parallel{ Work1();// #pragma omp master{ Work2();}// ...is practically identical to this:if(omp_get_thread_num()==0){ Work2();} Work3();}12345678910111213141516
⼗九、循环嵌套
如下代码并不会按照我们期望的⽅式运⾏:
#pragma omp parallel forfor(int y=0; y<25;++y){#pragma omp parallel for for(int x=0; x<80;++x) { tick(x,y); }}123456789
实际上内部的那个“parallel for"会被忽略,⾃始⾄终只创建了⼀个线程组。假如将上述代码改为如下所⽰,将⽆法通过编译:
#pragma omp parallel forfor(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); }}123456789
在OpenMP 3.0中,可以利⽤collapse指令来解决循环嵌套问题,如:
#pragma omp parallel for collapse(2)for(int y=0; y<25;++y){ for(int x=0; x<80;++x) { tick(x,y); }}12345678
collapse指令传递的数字就代表了循环嵌套的深度,这⾥为2层。
在OpenMP 2.5中,我们可以通过将多层循环改为单层循环的⽅法来达到⽬的,这样便⽆需循环嵌套:
#pragma omp parallel forfor(int pos=0; pos<(25*80);++pos){ int x = pos%80; int y = pos/80; tick(x,y);}1234567
然⽽重写这样的代码也并⾮易事,另⼀个办法是采⽤omp_set_nested⽅法开启循环嵌套⽀持,默认是关闭的:
omp_set_nested(1);#pragma omp parallel forfor(int y=0; y<25;++y){#pragma omp parallel for for(int x=0; x<80;++x) { tick(x,y); }}12345678910
现在内层循环中,也会创建⼀个⼤⼩为N的线程组,因此实际上我们将得到N*N个线程,这便会导致频繁的线程切换,会带来较⼤的性能损
失,这也就是为什么循环嵌套默认是关闭的。也许最好的⽅法就是将外层循环的parallel指令删除,只保留内层循环的parallel:
for(int y=0; y<25;++y){#pragma omp parallel for for(int x=0; x<80;++x) { tick(x,y); }}12345678
⼆⼗、取消线程(退出循环)
假如我们想要使⽤omp多线程来优化如下⽅法:
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;}123456789
我们最直观的想法应该是在for循环外加上“#pragma omp parallel for",但是让⼈失望的是这将⽆法通过编译。因为omp要求必须每个循环
迭代都能得到处理,因此不允许直接退出循环,这也就是说在循环中不能使⽤return、break、goto、throw等能够中断循环的语句。为了能
够提前退出循环,我们需要退出时,通知线程组的其他线程,让它们结束运⾏:
弃⽤omp,选择其他多线程编程,如pthread
通过共享变量,通知线程组其他线程
以下为使⽤布尔标记来通知其他线程的⽰例:
constchar* FindAnyNeedle(constchar* haystack, size_t size,char needle){constchar* result = NULL;bool done =false;#pragma omp parallel forfor(size_t p =0; p < size;++p){#pragma omp flush(do
ne)if(!done){/* Do work only if no 然⽽这样写有个缺点就是,即使done标记变为true了,其他线程仍然需要完成每次迭代,即使这些迭代是完全没有意义的。. 当然,我们也
可以不⽤上述的done标记:
constchar* FindAnyNeedle(constchar* haystack, size_t size,char needle){constchar* result = NULL;#pragma omp parallel forfor(size_t p =0; p < size;++p)if(haystack[p]== needle) result = haystack+p;return result;}12345678但是这也并没有完全解决问题,因为这样当⼀个线程已经到需要的结果是,也不能够避免其他线程继续运⾏,这也就造成了不必要的浪
费。
事实上,omp针对这个问题并没有很好的解决办法,如果确实需要,那只能求助于其他线程库了。
参考资料:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论