OpenMP

介紹

OpenMP是一個跨平台的多執行緒實現，主執行緒(順序的執行指令)生成一系列的子執行緒，並將任務劃分給這些子執行緒進行執行。這些子執行緒並列的執行，由執行時環境將執行緒分配給不同的處理器。
要進行並列執行的代碼片段需要進行相應的標記，用預編譯指令使得在代碼片段被執行前生成執行緒，每個執行緒會分配一個id，可以通過函式(called omp_get_thread_num())來獲得該值，該值是一個整數，主執行緒的id為0。在並列化的代碼執行結束後，子執行緒join到主執行緒中，並繼續執行程式。
預設情況下，各個執行緒獨立地執行並列區域的代碼。可以使用Work-sharing constructs來劃分任務，使每個執行緒執行其分配部分的代碼。通過這種方式，使用OpenMP可以實現任務並列和資料並列。
執行時環境分配給每個處理器的執行緒數取決於使用方法、機器負載和其他因素。執行緒的數目可以通過環境變數或者代碼中的函式來指定。在C/C++中，OpenMP的函式都聲明在表頭檔omp.h中。

歷史

OpenMP Architecture Review Board (ARB)於1997年10月發布了OpenMP for Fortran 1.0。次年的10月，發布了C/C++的標準。2000年，發布了Fortran語言的2.0版本，並於2002年發布了C/C++語言的2.0版本。2005年，包含Fortran和C/C++的2.5版本發布了。
在2008年5月發布了3.0版。3.0中的新功能包括任務(tasks)和任務結構(task construct)的概念。這些新功能總結在OpenMP3.0規範的附錄F中。
OpenMP規範的3.1版於2011年7月9日發布。
4.0版本在2013年7月發布，它增加或改進以下功能：支援加速器，原子性，錯誤處理，執行緒關聯，任務擴充，減少用戶定義的SIMD支援和Fortran 2003的支援。

特色

OpenMP提供的這種對於並列描述的高層抽象降低了並列編程的難度和複雜度，這樣程式設計師可以把更多的精力投入到並列演算法本身，而非其具體實現細節。對基於資料分集的多執行緒程式設計，OpenMP是一個很好的選擇。同時，使用OpenMP也提供了更強的靈活性，可以較容易的適應不同的並列系統配置。執行緒粒度和負載平衡等是傳統多執行緒程式設計中的難題，但在OpenMP中，OpenMP庫從程式設計師手中接管了部分這兩方面的工作。

語法

#pragma omp <directive> [clause[[,] clause] ...]

OpenMP的庫函式

OpenMP定義了20多個庫函式： 
1.void omp_set_num_threads(int _Num_threads);
在後續並列區域設定執行緒數，此呼叫只影響呼叫執行緒所遇到的同一級或內部巢狀級別的後續並列區域.說明：此函式只能在串行代碼部分呼叫.
2.int omp_get_num_threads(void);
返回當前執行緒數目.說明：如果在串行代碼中呼叫此函式，返回值為1.
3.int omp_get_max_threads(void);
如果在程式中此處遇到未使用 num_threads() 子句指定的活動並列區域,則返回程式的最大可用執行緒數量.說明：可以在串行或並列區域呼叫，通常這個最大數量由omp_set_num_threads()或OMP_NUM_THREADS環境變數決定.
4.int omp_get_thread_num(void);
返回當前執行緒id.id從1開始順序編號,主執行緒id是0.
5.int omp_get_num_procs(void);
返回程式可用的處理器數.
6.void omp_set_dynamic(int _Dynamic_threads);
啟用或禁用可用執行緒數的動態調整.(預設情況下啟用動態調整.)此呼叫只影響呼叫執行緒所遇到的同一級或內部巢狀級別的後續並列區域.如果 _Dynamic_threads 的值為非零值,啟用動態調整;否則,禁用動態調整.
7.int omp_get_dynamic(void);
確定在程式中此處是否啟用了動態執行緒調整.啟用了動態執行緒調整時返回非零值;否則,返回零值.
8.int omp_in_parallel(void);
確定執行緒是否在並列區域的動態範圍內執行.如果在活動並列區域的動態範圍內呼叫,則返回非零值;否則,返回零值.活動並列區域是指 IF 子句求值為 TRUE 的並列區域.
9.void omp_set_nested(int _Nested);
啟用或禁用巢狀並列操作.此呼叫只影響呼叫執行緒所遇到的同一級或內部巢狀級別的後續並列區域._Nested 的值為非零值時啟用巢狀並列操作;否則,禁用巢狀並列操作.預設情況下,禁用巢狀並列操作.
10.int omp_get_nested(void);
確定在程式中此處是否啟用了巢狀並列操作.啟用巢狀並列操作時返回非零值;否則,返回零值.
互斥鎖操作
巢狀鎖操作
功能
11.void omp_init_lock(omp_lock_t * _Lock);      
12. void omp_init_nest_lock(omp_nest_lock_t * _Lock);
初始化一個（巢狀）互斥鎖.
13.void omp_destroy_lock(omp_lock_t * _Lock);    
14.void omp_destroy_nest_lock(omp_nest_lock_t * _Lock);
結束一個（巢狀）互斥鎖的使用並釋放記憶體.
15.void omp_set_lock(omp_lock_t * _Lock);        
16.void omp_set_nest_lock(omp_nest_lock_t * _Lock);
獲得一個（巢狀）互斥鎖.
17.void omp_unset_lock(omp_lock_t * _Lock);      
18.void omp_unset_nest_lock(omp_nest_lock_t * _Lock); 
釋放一個（巢狀）互斥鎖.
19.int omp_test_lock(omp_lock_t * _Lock);        
20.int omp_test_nest_lock(omp_nest_lock_t * _Lock);
試圖獲得一個（巢狀）互斥鎖,並在成功時放回真（true）,失敗是返回假（false）.
21.double omp_get_wtime(void);
取得wall clock time,返回一個double的數,表示從過去的某一時刻經歷的時間,一般用於成對出現,進行時間比較.
此函式得到的時間是相對於執行緒的,也就是每一個執行緒都有自己的時間.
22.double omp_get_wtick(void);
得到clock ticks的秒數.

例子

在 omp parallel 段內的程式代碼由多執行緒來執行：
 int main(int argc, char* argv[])
 {
  #pragma omp parallel  
   printf("Hello, world.\n");

   return 1;
 }

執行結果

% gcc omp.c （由單線程來執行）
% ./a.out
Hello, world.

% gcc -fopenmp omp.c （由多線程來執行）
% ./a.out
Hello, world.
Hello, world.
Hello, world.
Hello, world.

環境變量

OpenMP可以使用環境變量 OMP_NUM_THREADS以控制執行執行緒的數量。

例子

% gcc -fopenmp omp.c 

% setenv OMP_NUM_THREADS 2（由2線程來執行）
setenv是CSH的指令

在bash shell 環境中 要用export 
% export OMP_NUM_THREADS=2 （由2線程來執行）

% ./a.out
Hello, world.
Hello, world.

爭議

作為高層抽象，OpenMP並不適合需要複雜的執行緒間同步和互斥的場合。 OpenMP的另一個缺點是不能在非共享記憶體系統（如電腦集群）上使用。在這樣的系統上，MPI使用較多。

編譯器支援

主流C/C++編譯器，如gcc與visual C++，都內在支援OpenMP。一般都必須在程式中#include <omp.h>
gcc編譯時需使用編譯選項-fopenmp。但是，如果編譯為目的檔與連結生成可執行檔是分開為兩步操作，那麼連結時需要給出附加庫gomp，否則會在連結時報錯「undefined reference to `omp_get_thread_num'"。
Visual C++需要在IDE的編譯選項->語言->支援OpenMP。這實際上使用了編譯選項/openmp