OpenMP 共享內(nèi)存的并行編程框架入門(mén)詳解
簡(jiǎn)介
OpenMP 一個(gè)非常易用的共享內(nèi)存的并行編程框架,它提供了一些非常簡(jiǎn)單易用的API,讓編程人員從復(fù)雜的并發(fā)編程當(dāng)中釋放出來(lái),專(zhuān)注于具體功能的實(shí)現(xiàn)。openmp 主要是通過(guò)編譯指導(dǎo)語(yǔ)句以及他的動(dòng)態(tài)運(yùn)行時(shí)庫(kù)實(shí)現(xiàn),在本篇文章當(dāng)中我們主要介紹 openmp 一些入門(mén)的簡(jiǎn)單指令的使用。
認(rèn)識(shí) openmp 的簡(jiǎn)單易用性
比如現(xiàn)在我們有一個(gè)任務(wù),啟動(dòng)四個(gè)線程打印 hello world
,我們看看下面 C
使用 pthread
的實(shí)現(xiàn)以及 C++
使用標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn),并對(duì)比他們和 openmp
的實(shí)現(xiàn)復(fù)雜性。
C 語(yǔ)言實(shí)現(xiàn)
#include <stdio.h> #include <pthread.h> void* func(void* args) { printf("hello world from tid = %ld\n", pthread_self()); return NULL; } int main() { pthread_t threads[4]; for(int i = 0; i < 4; i++) { pthread_create(&threads[i], NULL, func, NULL); } for(int i = 0; i < 4; i++) { pthread_join(threads[i], NULL); } return 0; }
上面文件編譯命令:gcc 文件名 -lpthread 。
C++ 實(shí)現(xiàn)
#include <thread> #include <iostream> void* func() { printf("hello world from %ld\n", std::this_thread::get_id()); return 0; } int main() { std::thread threads[4]; for(auto &t : threads) { t = std::thread(func); } for(auto &t : threads) { t.join(); } return EXIT_SUCCESS; }
上面文件編譯命令:g++ 文件名 lpthread 。
OpenMP 實(shí)現(xiàn)
#include <stdio.h> #include <omp.h> int main() { // #pragma 表示這是編譯指導(dǎo)語(yǔ)句 表示編譯器需要對(duì)下面的并行域進(jìn)行特殊處理 omp parallel 表示下面的代碼區(qū)域 {} 是一個(gè)并行域 num_threads(4) 表示一共有 4 個(gè)線程執(zhí)行 {} 內(nèi)的代碼 因此實(shí)現(xiàn)的效果和上面的效果是一致的 #pragma omp parallel num_threads(4) { printf("hello world from tid = %d\n", omp_get_thread_num()); // omp_get_thread_num 表示得到線程的線程 id } return 0; }
上面文件編譯命令:gcc 文件名 -fopenmp ,如果你使用了 openmp 的編譯指導(dǎo)語(yǔ)句的話需要在編譯選項(xiàng)上加上 -fopenmp
。
從上面的代碼來(lái)看,確實(shí) openmp
寫(xiě)并發(fā)程序的復(fù)雜度確實(shí)比 pthread
和 C++
低。openmp
相比起其他構(gòu)建并行程序的方式來(lái)說(shuō),使用 openmp
你可以更加關(guān)注具體的業(yè)務(wù)實(shí)現(xiàn),而不用太關(guān)心并發(fā)程序背后的啟動(dòng)與結(jié)束的過(guò)程,OenpMP 會(huì)幫我們實(shí)現(xiàn)很多細(xì)節(jié),讓程序的執(zhí)行符合我們的直覺(jué)。
opnemp 基本原理
在上文當(dāng)中我們寫(xiě)了一個(gè)非常簡(jiǎn)單的 openmp 程序,使用 4 個(gè)不同的線程分別打印 hello world
。我們仔細(xì)分析一下這個(gè)程序的執(zhí)行流程:
在 openmp 的程序當(dāng)中,你可以將程序用一個(gè)個(gè)的并行域分開(kāi),在并行域(parallel region)中,程序是有并發(fā)的,但是在并行域之外是沒(méi)有并發(fā)的,只有主線程(master)在執(zhí)行,整個(gè)過(guò)程如下圖所示:
現(xiàn)在我們用一個(gè)程序去驗(yàn)證上面的過(guò)程:
#include <stdio.h> #include <omp.h> #include <unistd.h> int main() { #pragma omp parallel num_threads(4) { printf("parallel region 1 thread id = %d\n", omp_get_thread_num()); sleep(1); } printf("after parallel region 1 thread id = %d\n", omp_get_thread_num()); #pragma omp parallel num_threads(4) { printf("parallel region 2 thread id = %d\n", omp_get_thread_num()); sleep(1); } printf("after parallel region 2 thread id = %d\n", omp_get_thread_num()); #pragma omp parallel num_threads(4) { printf("parallel region 3 thread id = %d\n", omp_get_thread_num()); sleep(1); } printf("after parallel region 3 thread id = %d\n", omp_get_thread_num()); return 0; }
程序執(zhí)行之后的一種輸出(還有很多其他的輸出形式,因?yàn)槭嵌嗑€程程序,線程的輸出是不確定的)如下所示:
parallel region 1 thread id = 0 parallel region 1 thread id = 3 parallel region 1 thread id = 1 parallel region 1 thread id = 2 after parallel region 1 thread id = 0 parallel region 2 thread id = 0 parallel region 2 thread id = 2 parallel region 2 thread id = 3 parallel region 2 thread id = 1 after parallel region 2 thread id = 0 parallel region 3 thread id = 0 parallel region 3 thread id = 1 parallel region 3 thread id = 3 parallel region 3 thread id = 2 after parallel region 3 thread id = 0
從上面的輸出我們可以了解到,id = 0 的線程就是主線程,在并行域內(nèi)部程序的輸出是沒(méi)有順序的,但是在并行域的外部是有序的,在并行域的開(kāi)始部分程序會(huì)進(jìn)行并發(fā)操作,但是在并行域的最后會(huì)有一個(gè)隱藏的同步點(diǎn),等待所有線程到達(dá)這個(gè)同步點(diǎn)之后程序才會(huì)繼續(xù)執(zhí)行,現(xiàn)在再看上文當(dāng)中 openmp
的執(zhí)行流圖的話就很清晰易懂了。
積分例子
現(xiàn)在我們使用一個(gè)簡(jiǎn)單的函數(shù)積分的例子去具體了解 openmp
在具體的使用場(chǎng)景下的并行。比如我們求函數(shù) x2x^2x2 的積分。
微元法的本質(zhì)就是將曲線下方的面積分割成一個(gè)一個(gè)的非常小的長(zhǎng)方形,然后將所有的長(zhǎng)方形的面積累加起來(lái),這樣得到最終的結(jié)果。
如果你不懂上面所談到的求解方法也沒(méi)關(guān)系,只需要知道我們需要使用 openmp 去計(jì)算一個(gè)計(jì)算量比較大的任務(wù)即可。根據(jù)上面微元法的公式我們有一個(gè)非常大的求和公式,如果是在單線程的情況下我們使用一個(gè)循環(huán)就可以了,但是現(xiàn)在我們有多個(gè)線程,那么我們可以讓每個(gè)線程求某一個(gè)區(qū)間的和,最后將各個(gè)區(qū)間的和加起來(lái)得到最終的結(jié)果,這就是在并發(fā)場(chǎng)景下的實(shí)現(xiàn)思路。
openmp
具體的實(shí)現(xiàn)代碼如下所示:
#include <stdio.h> #include <omp.h> #include <math.h> /// @brief 計(jì)算 x^2 一部分的面積 /// @param start 線程開(kāi)始計(jì)算的位置 /// @param end 線程結(jié)束計(jì)算的位置 /// @param delta 長(zhǎng)方形的邊長(zhǎng) /// @return 計(jì)算出來(lái)的面積 double x_square_partial_integral(double start, double end, double delta) { double s = 0; for(double i = start; i < end; i += delta) { s += pow(i, 2) * delta; } return s; } int main() { int s = 0; int e = 10; double sum = 0; #pragma omp parallel num_threads(32) reduction(+:sum) { // 根據(jù)線程號(hào)進(jìn)行計(jì)算區(qū)間的分配 // omp_get_thread_num() 返回的線程 id 從 0 開(kāi)始計(jì)數(shù) :0, 1, 2, 3, 4, ..., 31 double start = (double)(e - s) / 32 * omp_get_thread_num(); double end = (double)(e - s) / 32 * (omp_get_thread_num() + 1); sum = x_square_partial_integral(start, end, 0.0000001); } printf("sum = %lf\n", sum); return 0; }
在上面的代碼當(dāng)中 #pragma omp parallel num_threads(4)
表示啟動(dòng) 4 個(gè)線程執(zhí)行 {}
中的代碼,reduction(+:sum)
表示需要對(duì) sum
這個(gè)變量進(jìn)行一個(gè)規(guī)約操作,當(dāng) openmp 中的線程遇到 reduction
子句的時(shí)候首先會(huì)拷貝一份 sum
作為本地變量,然后在并行域當(dāng)中使用的就是每一個(gè)線程的本地變量,因?yàn)橛?reduction 的規(guī)約操作,因此在每個(gè)線程計(jì)算完成之后還需要將每個(gè)線程本地計(jì)算出來(lái)的值對(duì)操作符 + 進(jìn)行規(guī)約操作,也就是將每個(gè)線程計(jì)算得到的結(jié)果求和,最終將得到的結(jié)果賦值給我們?cè)?main 函數(shù)當(dāng)中定義的變量 sum
。最終我們打印的變量 sum
就是各個(gè)線程求和之后的結(jié)果。上面的代碼執(zhí)行過(guò)程大致如下圖所示:
注意事項(xiàng):你在編譯上述程序的時(shí)候需要加上編譯選項(xiàng) -fopenmp
啟動(dòng)openmp
編譯選項(xiàng)和 -lm
鏈接數(shù)學(xué)庫(kù)。
上面程序的執(zhí)行結(jié)果如下所示:
總結(jié)
在本篇文章當(dāng)中主要給大家介紹了 OpenMP 的基本使用和程序執(zhí)行的基本原理,在后續(xù)的文章當(dāng)中我們將仔細(xì)介紹各種 OpenMP
的子句和指令的使用方法
以上就是OpenMP 共享內(nèi)存的并行編程框架入門(mén)詳解的詳細(xì)內(nèi)容,更多關(guān)于OpenMP 共享內(nèi)存并行編程框架的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++初學(xué)者之根據(jù)輸入的任何一個(gè)正整數(shù),輸出可能被表示的連續(xù)正整數(shù)
這篇文章主要介紹了C++初學(xué)者之根據(jù)輸入的任何一個(gè)正整數(shù),輸出可能被表示的連續(xù)正整數(shù)的相關(guān)資料,需要的朋友可以參考下2016-03-03C++?Boost?Accumulators累加器詳細(xì)講解
Boost是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)。Boost庫(kù)是一個(gè)可移植、提供源代碼的C++庫(kù),作為標(biāo)準(zhǔn)庫(kù)的后備,是C++標(biāo)準(zhǔn)化進(jìn)程的開(kāi)發(fā)引擎之一,是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)2022-11-11C語(yǔ)言如何實(shí)現(xiàn)順序表(數(shù)據(jù)結(jié)構(gòu))
這篇文章主要介紹了C語(yǔ)言如何實(shí)現(xiàn)順序表(數(shù)據(jù)結(jié)構(gòu))問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08C++封裝成DLL并調(diào)用的實(shí)現(xiàn)
本文主要介紹了C++封裝成DLL并調(diào)用的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03基于C語(yǔ)言實(shí)現(xiàn)創(chuàng)意多彩貪吃蛇游戲
這篇文章主要介紹了如何利用C語(yǔ)言實(shí)現(xiàn)一個(gè)創(chuàng)意多彩貪吃蛇游戲,這是一個(gè)純C語(yǔ)言外加easyx庫(kù)的繪圖函數(shù)制作而成的有趣小游戲,無(wú)需引入額外資源,感興趣的可以動(dòng)手嘗試一下2022-08-08基于C++實(shí)現(xiàn)高精度計(jì)時(shí)器
chrono是C++ 11中的時(shí)間庫(kù),它提供了跨平臺(tái)的高精度時(shí)鐘解決方案,精確到納秒級(jí),本文主要為大家詳細(xì)介紹了如何使用chrono實(shí)現(xiàn)高精度計(jì)時(shí)器,感興趣的可以了解下2024-02-02類(lèi)成員函數(shù)的重載、覆蓋與隱藏之間的區(qū)別總結(jié)
以下是對(duì)類(lèi)成員函數(shù)的重載、覆蓋與隱藏之間的區(qū)別進(jìn)行了詳細(xì)的總結(jié)分析,需要的朋友可以過(guò)來(lái)參考下。希望對(duì)大家有所幫助2013-10-10