並行計算之OpenMP入門簡介

  在上一篇文章中介紹了並行計算的基礎概念,也順便介紹了OpenMP。程序員

  OpenMp提供了對於並行描述的高層抽象,下降了並行編程的難度和複雜度,這樣程序員能夠把更多的精力投入到並行算法自己,而非其具體實現細節。對基於數據分集的多線程程序設計,OpenMP是一個很好的選擇。同時,使用OpenMP也提供了更強的靈活性,能夠較容易的適應不一樣的並行系統配置。線程粒度和負載平衡等是傳統多線程程序設計中的難題,但在OpenMp中,OpenMp庫從程序員手中接管了部分這兩方面的工做。可是,做爲高層抽象,OpenMp並不適合須要複雜的線程間同步和互斥的場合。OpenMp的另外一個缺點是不能在非共享內存系統(如計算機集羣)上使用,通常在這樣的系統上,MPI使用較多。算法

  在Visual Studio中使用OpenMP其實很簡單,只要將 Project 的Properties中C/C++裏Language的OpenMP Support開啓(參數爲 /openmp),就可讓VC++在編譯時就能夠支持OpenMP 的語法了。而在編寫使用OpenMP 的程序時,添加#include <omp.h>便可。下面是一個實例:編程

#include <stdio.h>
#include <omp.h>
#include <windows.h>
#define MAX_VALUE 10000000

double _test(int value)
{
    int index = 0;
    double result = 0.0;
    for(index = value + 1; index < MAX_VALUE; index +=2 )
        result += 1.0 / index;

    return result;
}

void OpenMPTest()
{
    int index= 0;
    int time1 = 0;
    int time2 = 0;
    double value1 = 0.0, value2 = 0.0;
    double result[2];

    time1 = GetTickCount();
    for(index = 1; index < MAX_VALUE; index ++)
        value1 += 1.0 / index;

    time1 = GetTickCount() - time1;
    memset(result , 0, sizeof(double) * 2);
    time2 = GetTickCount();

#pragma omp parallel for
    for(index = 0; index < 2; index++)
        result[index] = _test(index);

    value2 = result[0] + result[1];
    time2 = GetTickCount() - time2;

    printf("time1 = %d,time2 = %d\n",time1,time2);
    return;
}

int main()
{
    OpenMPTest();

    system("pause");
    return 0;
}
View Code

  在這裏例子中用到了一個關鍵的語句:windows

#pragma omp parallel for

  這個句子表明了C++中使用OpenMP的基本語法規則:pragma omp 指令 [子句[子句]…]多線程

1. OpenMP指令與庫函數ide

  OpenMP包括如下指令函數

  • parallel:用在一個代碼段以前,表示這段代碼將被多個線程並行執行
  • for:用於for循環以前,將循環分配到多個線程中並行執行,必須保證每次循環之間無相關性
  • parallel for:parallel 和 for語句的結合,也是用在一個for循環以前,表示for循環的代碼將被多個線程並行執行
  • sections:用在可能會被並行執行的代碼段以前
  • parallel sections:parallel和sections兩個語句的結合
  • critical:用在一段代碼臨界區以前
  • single:用在一段只被單個線程執行的代碼段以前,表示後面的代碼段將被單線程執行
  • barrier:用於並行區內代碼的線程同步,全部線程執行到barrier時要中止,直到全部線程都執行到barrier時才繼續往下執行
  • atomic:用於指定一塊內存區域被制動更新
  • master:用於指定一段代碼塊由主線程執行
  • ordered:用於指定並行區域的循環按順序執行
  • threadprivate:用於指定一個變量是線程私有的

  OpenMP除上述指令外,還有一些庫函數,下面列出幾個經常使用的庫函數測試

  • omp_get_num_procs:返回運行本線程的多處理機的處理器個數
  • omp_get_num_threads:返回當前並行區域中的活動線程個數
  • omp_get_thread_num:返回線程號
  • omp_set_num_threads:設置並行執行代碼時的線程個數
  • omp_init_lock:初始化一個簡單鎖
  • omp_set_lock:上鎖操做
  • omp_unset_lock:解鎖操做,要和omp_set_lock函數配對使用
  • omp_destroy_lock:omp_init_lock函數的配對操做函數,關閉一個鎖

  OpenMP還包括如下子句atom

  • private:指定每一個線程都有它本身的變量私有副本
  • firstprivate:指定每一個線程都有它本身的變量私有副本,而且變量要被繼承主線程中的初值
  • lastprivate:主要是用來指定將線程中的私有變量的值在並行處理結束後複製回主線程中的對應變量
  • reduce:用來指定一個或多個變量是私有的,而且在並行處理結束後這些變量要執行指定的運算
  • nowait:忽略指定中暗含的等待
  • num_threads:指定線程的個數
  • schedule:指定如何調度for循環迭代
  • shared:指定一個或多個變量爲多個線程間的共享變量
  • ordered:用來指定for循環的執行要按順序執行
  • copyprivate:用於single指令中的指定變量爲多個線程的共享變量
  • copyin:用來指定一個threadprivate的變量的值要用主線程的值進行初始化。
  • default:用來指定並行處理區域內的變量的使用方式,缺省是shared

2. parallel指令用法spa

  parallel 是用來構造一個並行塊的,也可使用其餘指令如for、sections等和它配合使用。其用法以下:

#pragma omp parallel [for | sections] [子句[子句]…]
{
  // 須要並行執行的代碼
}

   例如,能夠寫一個簡單的並行輸出提示信息的代碼:

#pragma omp parallel num_threads(8)
{
    printf(「Hello, World!, ThreadId=%d\n」, omp_get_thread_num() );
}

  在本機測試將會獲得以下結果:

  結果代表,printf函數被建立了8個線程來執行,而且每個線程執行的前後次序並不肯定。和傳統的建立線程函數比起來,OpenMP至關於爲一個線程入口函數重複調用建立線程函數來建立線程並等待線程執行完。若是在上面的代碼中去掉num_threads(8)來指定線程數目,那麼將根據實際CPU核心數目來建立線程數。

3. for指令用法

  for指令則是用來將一個for循環分配到多個線程中執行。for指令通常能夠和parallel指令合起來造成parallel for指令使用,也能夠單獨用在parallel語句的並行塊中。其語法以下:

#pragma omp [parallel] for [子句]
    for循環語句

  例若有這樣一個例子:

#pragma omp parallel for
for ( int j = 0; j < 4; j++ )
{
    printf("j = %d, ThreadId = %d\n", j, omp_get_thread_num());
}

  能夠獲得以下結果:

  從結果能夠看出,for循環的語句被分配到不一樣的線程中分開執行了。須要注意的是,若是不添加parallel關鍵字,那麼四次循環將會在同一個線程裏執行,結果將會是下面這樣的:

4. sections和section的用法

  section語句是用在sections語句裏用來將sections語句裏的代碼劃分紅幾個不一樣的段,每段都並行執行。用法以下:

#pragma omp [parallel] sections [子句]
{
    #pragma omp section
    {
        // 代碼塊
    }
}

  例若有這樣一個例子:

#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());
}

  能夠獲得以下結果:

  結果代表,每個section內部的代碼都是(分配到不一樣的線程中)並行執行的。使用section語句時,須要注意的是這種方式須要保證各個section裏的代碼執行時間相差不大,不然某個section執行時間比其餘section長太多就達不到並行執行的效果了。

  若是將上面的代碼拆分紅兩個sections,即:

#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 parallel 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());
}

  產生的結果將會是這樣的:

  能夠看出,兩個sections之間是串行執行的,而section內部則是並行執行的。

 

小節:

  用for語句來分攤任務是由系統自動進行的,只要每次循環間沒有時間上的差距,那麼分攤是很均勻的,使用section來劃分線程是一種手工劃分線程的方式,最終並行性的好壞依賴於程序員。

  本篇文章中講的幾個OpenMP指令parallel, for, sections, section實際上都是用來如何建立線程的,這種建立線程的方式比起傳統調用建立線程函數建立線程要更方便,而且更高效。 

相關文章
相關標籤/搜索