OpenMP并行程序設(shè)計(二)
1.fork/join并行執(zhí)行模式的概念
OpenMP是一個編譯器指令和庫函數(shù)的集合,主要是為共享式存儲計算機(jī)上的并行程序設(shè)計使用的。
前 面一篇文章中已經(jīng)試用了OpenMP的一個Parallel for指令。從上篇文章中我們也可以發(fā)現(xiàn)OpenMP并行執(zhí)行的程序要全部結(jié)束后才能執(zhí)行后面的非并行部分的代碼。這就是標(biāo)準(zhǔn)的并行模式 fork/join式并行模式,共享存儲式并行程序就是使用fork/join式并行的。
標(biāo)準(zhǔn)并行模式執(zhí)行代碼的基本思想是,程序開始時只有一個主線程,程序中的串行部分都由主線程執(zhí)行,并行的部分是通過派生其他線程來執(zhí)行,但是如果并行部分沒有結(jié)束時是不會執(zhí)行串行部分的,如上一篇文章中的以下代碼:
- int main(int argc, char* argv[])
- {
- clock_t t1 = clock();
- #pragma omp parallel for
- for ( int j = 0; j < 2; j++ ){
- test();
- }
- clock_t t2 = clock();
- printf("Total time = %d/n", t2-t1);
- test();
- return 0;
- }
2.OpenMP指令和庫函數(shù)介紹
下面來介紹OpenMP的基本指令和常用指令的用法,
在C/C++中,OpenMP指令使用的格式為
#pragma omp 指令 [子句[子句]…]
前面提到的parallel for就是一條指令,有些書中也將OpenMP的“指令”叫做“編譯指導(dǎo)語句”,后面的子句是可選的。例如:
#pragma omp parallel private(i, j)
parallel 就是指令, private是子句
為敘述方便把包含#pragma和OpenMP指令的一行叫做語句,如上面那行叫parallel語句。
OpenMP的指令有以下一些:
parallel,用在一個代碼段之前,表示這段代碼將被多個線程并行執(zhí)行
for,用于for循環(huán)之前,將循環(huán)分配到多個線程中并行執(zhí)行,必須保證每次循環(huán)之間無相關(guān)性。
parallel for, parallel 和 for語句的結(jié)合,也是用在一個for循環(huán)之前,表示for循環(huán)的代碼將被多個線程并行執(zhí)行。
sections,用在可能會被并行執(zhí)行的代碼段之前
parallel sections,parallel和sections兩個語句的結(jié)合
critical,用在一段代碼臨界區(qū)之前
single,用在一段只被單個線程執(zhí)行的代碼段之前,表示后面的代碼段將被單線程執(zhí)行。
flush,
barrier,用于并行區(qū)內(nèi)代碼的線程同步,所有線程執(zhí)行到barrier時要停止,直到所有線程都執(zhí)行到barrier時才繼續(xù)往下執(zhí)行。
atomic,用于指定一塊內(nèi)存區(qū)域被制動更新
master,用于指定一段代碼塊由主線程執(zhí)行
ordered, 用于指定并行區(qū)域的循環(huán)按順序執(zhí)行
threadprivate, 用于指定一個變量是線程私有的。
OpenMP除上述指令外,還有一些庫函數(shù),下面列出幾個常用的庫函數(shù):
omp_get_num_procs, 返回運行本線程的多處理機(jī)的處理器個數(shù)。
omp_get_num_threads, 返回當(dāng)前并行區(qū)域中的活動線程個數(shù)。
omp_get_thread_num, 返回線程號
omp_set_num_threads, 設(shè)置并行執(zhí)行代碼時的線程個數(shù)
omp_init_lock, 初始化一個簡單鎖
omp_set_lock, 上鎖操作
omp_unset_lock, 解鎖操作,要和omp_set_lock函數(shù)配對使用。
omp_destroy_lock, omp_init_lock函數(shù)的配對操作函數(shù),關(guān)閉一個鎖
OpenMP的子句有以下一些
private, 指定每個線程都有它自己的變量私有副本。
firstprivate,指定每個線程都有它自己的變量私有副本,并且變量要被繼承主線程中的初值。
lastprivate,主要是用來指定將線程中的私有變量的值在并行處理結(jié)束后復(fù)制回主線程中的對應(yīng)變量。
reduce,用來指定一個或多個變量是私有的,并且在并行處理結(jié)束后這些變量要執(zhí)行指定的運算。
nowait,忽略指定中暗含的等待
num_threads,指定線程的個數(shù)
schedule,指定如何調(diào)度for循環(huán)迭代
shared,指定一個或多個變量為多個線程間的共享變量
ordered,用來指定for循環(huán)的執(zhí)行要按順序執(zhí)行
copyprivate,用于single指令中的指定變量為多個線程的共享變量
copyin,用來指定一個threadprivate的變量的值要用主線程的值進(jìn)行初始化。
default,用來指定并行處理區(qū)域內(nèi)的變量的使用方式,缺省是shared
3.parallel 指令的用法
parallel 是用來構(gòu)造一個并行塊的,也可以使用其他指令如for、sections等和它配合使用。
在C/C++中,parallel的使用方法如下:
- #pragma omp parallel [for | sections] [子句[子句]…]
- {
- //代碼
- }
- parallel語句后面要跟一個大括號對將要并行執(zhí)行的代碼括起來。
- void main(int argc, char *argv[]) {
- #pragma omp parallel
- {
- printf(“Hello, World!/n”);
- }
- }
- 執(zhí)行以上代碼將會打印出以下結(jié)果
- Hello, World!
- Hello, World!
- Hello, World!
- Hello, World!
- 可以看得出parallel語句中的代碼被執(zhí)行了四次,說明總共創(chuàng)建了4個線程去執(zhí)行parallel語句中的代碼。
- 也可以指定使用多少個線程來執(zhí)行,需要使用num_threads子句:
- void main(int argc, char *argv[]) {
- #pragma omp parallel num_threads(8)
- {
- printf(“Hello, World!, ThreadId=%d/n”, omp_get_thread_num() );
- }
- }
- 執(zhí)行以上代碼,將會打印出以下結(jié)果:
- Hello, World!, ThreadId = 2
- Hello, World!, ThreadId = 6
- Hello, World!, ThreadId = 4
- Hello, World!, ThreadId = 0
- Hello, World!, ThreadId = 5
- Hello, World!, ThreadId = 7
- Hello, World!, ThreadId = 1
- Hello, World!, ThreadId = 3
ThreadId的不同可以看出創(chuàng)建了8個線程來執(zhí)行以上代碼。所以parallel指令是用來為一段代碼創(chuàng)建多個線程來執(zhí)行它的。parallel塊中的每行代碼都被多個線程重復(fù)執(zhí)行。
和傳統(tǒng)的創(chuàng)建線程函數(shù)比起來,相當(dāng)于為一個線程入口函數(shù)重復(fù)調(diào)用創(chuàng)建線程函數(shù)來創(chuàng)建線程并等待線程執(zhí)行完。
4.for指令的使用方法
for指令則是用來將一個for循環(huán)分配到多個線程中執(zhí)行。for指令一般可以和parallel指令合起來形成parallel for指令使用,也可以單獨用在parallel語句的并行塊中。
#pragma omp [parallel] for [子句]
for循環(huán)語句
先看看單獨使用for語句時是什么效果:
- 先看看單獨使用for語句時是什么效果:
- int j = 0;
- #pragma omp for
- for ( j = 0; j < 4; j++ ){
- printf(“j = %d, ThreadId = %d/n”, j, omp_get_thread_num());
- }
- 執(zhí)行以上代碼后打印出以下結(jié)果
- j = 0, ThreadId = 0
- j = 1, ThreadId = 0
- j = 2, ThreadId = 0
- j = 3, ThreadId = 0
- 從結(jié)果可以看出四次循環(huán)都在一個線程里執(zhí)行,可見for指令要和parallel指令結(jié)合起來使用才有效果:
- 如以下代碼就是parallel 和for一起結(jié)合成parallel for的形式使用的:
- int j = 0;
- #pragma omp parallel for
- for ( j = 0; j < 4; j++ ){
- printf(“j = %d, ThreadId = %d/n”, j, omp_get_thread_num());
- }
- 執(zhí)行后會打印出以下結(jié)果:
- j = 0, ThreadId = 0
- j = 2, ThreadId = 2
- j = 1, ThreadId = 1
- j = 3, ThreadId = 3
- 可見循環(huán)被分配到四個不同的線程中執(zhí)行。
- 上面這段代碼也可以改寫成以下形式:
- int j = 0;
- #pragma omp parallel
- {
- #pragma omp for
- for ( j = 0; j < 4; j++ ){
- printf(“j = %d, ThreadId = %d/n”, j, omp_get_thread_num());
- }
- }
執(zhí)行以上代碼會打印出以下結(jié)果:
j = 1, ThreadId = 1
j = 3, ThreadId = 3
j = 2, ThreadId = 2
j = 0, ThreadId = 0
在一個parallel 塊中也可以有多個for語句,如:
- int j;
- #pragma omp parallel
- {
- #pragma omp for
- for ( j = 0; j < 100; j++ ){
- …
- }
- #pragma omp for
- for ( j = 0; j < 100; j++ ){
- …
- }
- …
- }
for 循環(huán)語句中,書寫是需要按照一定規(guī)范來寫才可以的,即for循環(huán)小括號內(nèi)的語句要按照一定的規(guī)范進(jìn)行書寫,for語句小括號里共有三條語句
for( i=start; i < end; i++)
i=start; 是for循環(huán)里的第一條語句,必須寫成 “變量=初值” 的方式。如 i=0
i < end;是for循環(huán)里的第二條語句,這個語句里可以寫成以下4種形式之一:
變量 < 邊界值
變量 <= 邊界值
變量 > 邊界值
變量 >= 邊界值
如 i>10 i< 10 i>=10 i>10 等等
最后一條語句i++可以有以下9種寫法之一
- i++
- ++i
- i--
- --i
- i += inc
- i -= inc
- ii = i + inc
- i = inc + i
- ii = i –inc
例如i += 2; i -= 2;i = i + 2;i = i - 2;都是符合規(guī)范的寫法。
5.sections和section指令的用法
section語句是用在sections語句里用來將sections語句里的代碼劃分成幾個不同的段,每段都并行執(zhí)行。用法如下:
- #pragma omp [parallel] sections [子句]
- {
- #pragma omp section
- {
- 代碼塊
- }
- }
先看一下以下的例子代碼:
- void main(int argc, char *argv)
- {
- #pragma omp parallel sections {
- #pragma omp section
- printf(“section 1 ThreadId = %d/n”, omp_get_thread_num());
- #pragma omp section
- printf(“section 2 ThreadId = %d/n”, omp_get_thread_num());
- #pragma omp section
- printf(“section 3 ThreadId = %d/n”, omp_get_thread_num());
- #pragma omp section
- printf(“section 4 ThreadId = %d/n”, omp_get_thread_num());
- }
執(zhí)行后將打印出以下結(jié)果:
section 1 ThreadId = 0
section 2 ThreadId = 2
section 4 ThreadId = 3
section 3 ThreadId = 1
從結(jié)果中可以發(fā)現(xiàn)第4段代碼執(zhí)行比第3段代碼早,說明各個section里的代碼都是并行執(zhí)行的,并且各個section被分配到不同的線程執(zhí)行。
使用section語句時,需要注意的是這種方式需要保證各個section里的代碼執(zhí)行時間相差不大,否則某個section執(zhí)行時間比其他section過長就達(dá)不到并行執(zhí)行的效果了。
上面的代碼也可以改寫成以下形式:
- void main(int argc, char *argv)
- {
- #pragma omp parallel {
- #pragma omp sections
- {
- #pragma omp section
- printf(“section 1 ThreadId = %d/n”, omp_get_thread_num());
- #pragma omp section
- printf(“section 2 ThreadId = %d/n”, omp_get_thread_num());
- }
- #pragma omp sections
- {
- #pragma omp section
- printf(“section 3 ThreadId = %d/n”, omp_get_thread_num());
- #pragma omp section
- printf(“section 4 ThreadId = %d/n”, omp_get_thread_num());
- }
- }
執(zhí)行后將打印出以下結(jié)果:
section 1 ThreadId = 0
section 2 ThreadId = 3
section 3 ThreadId = 3
section 4 ThreadId = 1
這種方式和前面那種方式的區(qū)別是,兩個sections語句是串行執(zhí)行的,即第二個sections語句里的代碼要等第一個sections語句里的代碼執(zhí)行完后才能執(zhí)行。
用for語句來分?jǐn)偸怯上到y(tǒng)自動進(jìn)行,只要每次循環(huán)間沒有時間上的差距,那么分?jǐn)偸呛芫鶆虻模褂胹ection來劃分線程是一種手工劃分線程的方式,最終并行性的好壞得依賴于程序員。
本篇文章中講的幾個OpenMP指令parallel, for, sections, section實際上都是用來如何創(chuàng)建線程的,這種創(chuàng)建線程的方式比起傳統(tǒng)調(diào)用創(chuàng)建線程函數(shù)創(chuàng)建線程要更方便,并且更高效。
當(dāng)然,創(chuàng)建線程后,線程里的變量是共享的還是其他方式,主線程中定義的變量到了并行塊內(nèi)后還是和傳統(tǒng)創(chuàng)建線程那種方式一樣的嗎?創(chuàng)建的線程是如何調(diào)度的?等等諸如此類的問題到下一篇文章中進(jìn)行講解。
原文鏈接:http://blog.csdn.net/drzhouweiming/article/details/1175848