在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_threads()用於獲取線程組中的線程數。所以線程組中每個線程都運行了for循環中的不同部分。
這里提到for循環的不同部分在線程組中的不同線程中執行的,線程組是在程序遇到"#pragma omp parallel"時創建,在程序塊(parallel后的”{…}"或者語句)結束后,線程組中的只有一個主線程。因此上面示例中的代碼事實上是以下代碼的縮寫:
#pragma omp parallel
{
#pragma omp for
for (int i = 0; i < 10; ++i)
{
printf("%d ", i);
}
}
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 firstprivate by default traverse(p->right); process(p);}123456789101112
上面的示例中,當處理當前節點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 by default postorder_traverse(p->right);#pragma omp taskwait process(p);}12345678910111213
以下示例演示了如何利用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 firstprivate by default}}}}12345678910111213141516
十、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){ datastructure.reorganize();}...#pragma omp critical(dataupdate){ datastructure.reorganize_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 section */}}12345678
在上面的例子中,變量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;}1234567891011121314
注:最后的聚合實際是包括主線程中共享變量的初始值一起的,在階乘的例子中,如果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 parallel block are finished. CodeContinues(); #pragma omp parallel{#pragma omp for nowaitfor(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();1234567891011121314151617181920212223
十八、single 和 master 指令
single指令相關的代碼塊只運行一個線程執行,但並不限定具體哪一個線程來執行,其它線程必須跳過這個代碼塊,並在代碼塊后wait,直到執行這段代碼的線程完成。
#pragma omp parallel{ Work1();#pragma omp single{ Work2();} Work3();}123456789
以上代碼中,work1()和work3()會在線程組中所有線程都 運行一遍,但是work2()只會在一個線程中執行,即只會執行一遍。
master指令則指定其相關的代碼塊必須在主線程中執行,且其它線程不必在代碼塊后阻塞。
#pragma omp parallel{ Work1();// This...#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(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;}12345678910111213141516171819202122
然而這樣寫有個缺點就是,即使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;}123456789
但是這也並沒有完全解決問題,因為這樣當一個線程已經找到需要的結果是,也不能夠避免其他線程繼續運行,這也就造成了不必要的浪費。
事實上,omp針對這個問題並沒有很好的解決辦法,如果確實需要,那只能求助於其他線程庫了。
參考資料:
https://blog.csdn.net/gengshenghong/article/details/7003110
https://www.cnblogs.com/liangliangh/p/3565136.html
https://blog.csdn.net/chen134225/article/details/107396923?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-0.essearch_pc_relevant&spm=1001.2101.3001.4242
