OpenMP概述

根據計算平臺和規模的不同,並行計算可以分爲兩種:第一種是基於單一計算機系統的多核處理器或多處理器進行多線程並行計算,採用共享存儲的方式,主要的標準有OpenMP,如下左圖所示;第二種就是基於多臺計算機組件的集羣(Cluster)計算系統進行並行計算,採用消息傳遞方式,主要的標準有MPI,如下右圖所示。本文將主要介紹多線程方式的並行計算。

首先來了解一下單核處理器上程序運行方式,系統中包括操作系統和應用程序等都以進程(Process)形式存在,當程序結束時這個進程也就跟着消亡。每個進程中至少包含一個線程(Thread),一個線程用於完成程序的某個功能,一個程序中一般都包含多個線程,所有的這些線程在系統中都成隊列形式。對於一個核心的處理器來說,某一時刻,它只能處理一個線程。這個線程處理之後就處理下一個線程,依次循環處理。由於CPU的主頻都非常高,如Intel的奔四可到3GHz,所以每個線程處理的時間都非常短,以致我們並不會察覺。但是它們實際上是以串行的形式在CPU上運行。所以在物理上,對於單核處理器來說,是無法實現物理上的並行。在單核處理器上,即使使用多線程來分發程序,但實際上還是以單線程的形式在運行。

如果將執行核增加一個,那麼在同一時刻,將會有兩個線程在運行,這樣將會在一定程度上提供計算機的運行速度,但對於某些單線程的程序過程來說,實際情況並沒用得到改善。至於系統中的線程怎麼分發到兩個核上,這就是操作系統的任務了。很多應用程序都不止包含一個線程,一般都包含有多個線程,如用Spy++工具查看系統所有的線程,如下圖所示:

這多個線程如果在一個執行核上運行,它們呈一個隊列來執行。如果在兩個執行核上運行的話,它們將呈兩隊來執行。如果某個程序中包括有四個線程,而這四個線程又分別在兩個核上執行,那麼執行該程序將節約一半的時間。但如果該程序是單線程,無論是單核還是多核,運行該程序所需的時間都將是一樣的。所以,以前有人在某單線程程序中將一些程序分開放在兩個線程中分別執行,效率得到了提高,節約將近一倍的時間,其原因就在此。

OpenMP提供的就是一個多線程編程標準,現在.Net平臺也提供了並行編程的System.Threading.Tasks.Parallel類,可以使多個線程能夠同時執行。如果直接採用多個線程去實現並行,需要經常處理線程或線程池,採用這些多線程編程標準可以簡化並行開發,也不必直接處理線程或線程池。

在C/C++中採用OpenMP指令的格式如下:

#pragma omp …

關鍵字omp表示這個指令是OpenMP指令,所以它會被OpenMP編譯器處理,其他非OpenMP編譯器將不會理會。由於OpenMP指令都預先定義了,所以很容易被識別出來。這樣程序員編寫的並行代碼就可以在不同的平臺上運行。如果平臺不支持並行,也會直接跳過並行指令把程序當作串行程序來運行。

OpenMP提供了兩種控制並行的結構:第一種就是提供了一個用於創建多線程程序的指令,這些線程相互之間是並行的,這個指令實際上就是創建了一些線程去執行並形體中的程序;第二種就是對已存在的並行結構進行分工的指令,像循環中的do指令(Fortran)或for(C/C ++)。

一個OpenMp程序通常都是從一個單線程程序開始,我們通常把這個單線程程序叫做主線程(Master Thread),在主線程的程序中應該要包含整個程序中需要使用的數據變量,包括全局變量。當主線程遇到並行結構時,將會創建新的線程來執行並形體中的程序。每個線程都會獨立的執行並形體中的程序,相互之間不會影響,但是它們之間可以共用主線程裏面定義的全局變量。在並行過程中具體哪些變量是共享的、哪些變量是線程私有的,可以通過條件clauses(…)對每個變量進行指定,這些條件用於並行線程中決定哪些可用。一個變量可以有三種類型,即sharedprivatereduction。其中shared表示在並行結構中將有一個單獨的內存位置來存放這個變量,所有的並行線程都可以使用這個變量,所有的並行線程將共享這塊內存地址,因此,線程間的通信通過普通的讀寫操作方式就可以實現,當然,這個變量也可以隨意被任何一個線程修改。相反,private變量將會有多個內存地址,每個線程裏面一個。這個變量的所有讀寫操作都只限於本線程,其他線程是無法訪問本線程中該變量的內存地址的。所以,一般都用於定義臨時變量。reduction就有點難理解了,它具有sharedprivate的特徵,就像它的字面意思一樣,reduction屬性用於需要下降的變量(指值的減少)。Reduction操作在很多程序中都非常重要,最常見的例子就是計算並行結構中最後的臨時局部變量的總和。除了這三種之外,OpenMP還提供了許多其他數據屬性參數。

多個OpenMP線程之間可以採用共享變量(shared)通過簡單的讀寫操作來進行通信,但是這需要在多個線程中協調一致。如果協調不一致,可能會出現多個線程同時修改這個變量,或者這個線程正在讀而那個線程又正在寫,這些潛在的衝突都將導致數據的錯誤,因此,在多線程中必須避免這種情況,必須明確地協調好。在並行程序中設置同步(synchronization)就可以協調這些執行的多線程。最常見的兩種情況就是相互排斥和事件同步,互斥就是在這段代碼中通過一個線程不讓其他線程讀取這個共享變量。當很多線程正在修改同一個變量時,爲了確保這個變量值是對的,在修改之前就需要進行互斥存取。OpenMP中提供了一個critical指標來表示互斥。事件同步常用於表示多線程間的事件,最簡單的形式就是barrier阻塞。在並行程序中barrier指標表示在某點處每個線程都在這等待其他的線程也運行到這裏,一旦所有的線程都達到這個點後,它們又繼續執行。就像跑步的時候,有的人跑得快,有的人跑得慢,在跑了兩圈的時候,所有人都在這裏等待最後一個人,當最後一個人也跑到兩圈的時候,然後大家又接着繼續跑,這個過程就稱爲barrier阻塞。barrier指令能保證所有線程都執行了在barrier之前的代碼。

一個典型的並行程序結構如下圖所示:

OpenMP API是一套非常簡便的共享存儲並行計算應用程序接口,它是一個多線程、共享存儲的模型。線程間通過共享的變量進行交換,並可以通過線程同步來防止數據衝突,當然,同步是需要耗費很多的資源的,所以儘量減少同步的需要。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章