網站首頁 編程語言 正文
簡介
OpenMP 一個非常易用的共享內存的并行編程框架,它提供了一些非常簡單易用的API,讓編程人員從復雜的并發編程當中釋放出來,專注于具體功能的實現。openmp 主要是通過編譯指導語句以及他的動態運行時庫實現,在本篇文章當中我們主要介紹 openmp 一些入門的簡單指令的使用。
認識 openmp 的簡單易用性
比如現在我們有一個任務,啟動四個線程打印 hello world
,我們看看下面 C
使用 pthread
的實現以及 C++
使用標準庫的實現,并對比他們和 openmp
的實現復雜性。
C 語言實現
#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++ 實現
#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 實現
#include <stdio.h> #include <omp.h> int main() { // #pragma 表示這是編譯指導語句 表示編譯器需要對下面的并行域進行特殊處理 omp parallel 表示下面的代碼區域 {} 是一個并行域 num_threads(4) 表示一共有 4 個線程執行 {} 內的代碼 因此實現的效果和上面的效果是一致的 #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 的編譯指導語句的話需要在編譯選項上加上 -fopenmp
。
從上面的代碼來看,確實 openmp
寫并發程序的復雜度確實比 pthread
和 C++
低。openmp
相比起其他構建并行程序的方式來說,使用 openmp
你可以更加關注具體的業務實現,而不用太關心并發程序背后的啟動與結束的過程,OenpMP 會幫我們實現很多細節,讓程序的執行符合我們的直覺。
opnemp 基本原理
在上文當中我們寫了一個非常簡單的 openmp 程序,使用 4 個不同的線程分別打印 hello world
。我們仔細分析一下這個程序的執行流程:
在 openmp 的程序當中,你可以將程序用一個個的并行域分開,在并行域(parallel region)中,程序是有并發的,但是在并行域之外是沒有并發的,只有主線程(master)在執行,整個過程如下圖所示:
現在我們用一個程序去驗證上面的過程:
#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; }
程序執行之后的一種輸出(還有很多其他的輸出形式,因為是多線程程序,線程的輸出是不確定的)如下所示:
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 的線程就是主線程,在并行域內部程序的輸出是沒有順序的,但是在并行域的外部是有序的,在并行域的開始部分程序會進行并發操作,但是在并行域的最后會有一個隱藏的同步點,等待所有線程到達這個同步點之后程序才會繼續執行,現在再看上文當中 openmp
的執行流圖的話就很清晰易懂了。
積分例子
現在我們使用一個簡單的函數積分的例子去具體了解 openmp
在具體的使用場景下的并行。比如我們求函數 x2x^2x2 的積分。
微元法的本質就是將曲線下方的面積分割成一個一個的非常小的長方形,然后將所有的長方形的面積累加起來,這樣得到最終的結果。
如果你不懂上面所談到的求解方法也沒關系,只需要知道我們需要使用 openmp 去計算一個計算量比較大的任務即可。根據上面微元法的公式我們有一個非常大的求和公式,如果是在單線程的情況下我們使用一個循環就可以了,但是現在我們有多個線程,那么我們可以讓每個線程求某一個區間的和,最后將各個區間的和加起來得到最終的結果,這就是在并發場景下的實現思路。
openmp
具體的實現代碼如下所示:
#include <stdio.h> #include <omp.h> #include <math.h> /// @brief 計算 x^2 一部分的面積 /// @param start 線程開始計算的位置 /// @param end 線程結束計算的位置 /// @param delta 長方形的邊長 /// @return 計算出來的面積 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) { // 根據線程號進行計算區間的分配 // omp_get_thread_num() 返回的線程 id 從 0 開始計數 :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; }
在上面的代碼當中 #pragma omp parallel num_threads(4)
表示啟動 4 個線程執行 {}
中的代碼,reduction(+:sum)
表示需要對 sum
這個變量進行一個規約操作,當 openmp 中的線程遇到 reduction
子句的時候首先會拷貝一份 sum
作為本地變量,然后在并行域當中使用的就是每一個線程的本地變量,因為有 reduction 的規約操作,因此在每個線程計算完成之后還需要將每個線程本地計算出來的值對操作符 + 進行規約操作,也就是將每個線程計算得到的結果求和,最終將得到的結果賦值給我們在 main 函數當中定義的變量 sum
。最終我們打印的變量 sum
就是各個線程求和之后的結果。上面的代碼執行過程大致如下圖所示:
注意事項:你在編譯上述程序的時候需要加上編譯選項 -fopenmp
啟動openmp
編譯選項和 -lm
鏈接數學庫。
上面程序的執行結果如下所示:
總結
在本篇文章當中主要給大家介紹了 OpenMP 的基本使用和程序執行的基本原理,在后續的文章當中我們將仔細介紹各種 OpenMP
的子句和指令的使用方法
原文鏈接:https://juejin.cn/post/7160175377543856158
相關推薦
- 2022-12-03 Flutter狀態管理Bloc使用示例詳解_Android
- 2022-01-09 uview 使用scroll-view以及swiper 做tabs
- 2022-03-30 一篇文章帶你了解C語言的選擇結構_C 語言
- 2022-04-05 詳解C#如何實現讀寫ini文件_C#教程
- 2022-08-22 GoFrame實現順序性校驗示例詳解_Golang
- 2023-02-12 Jupyter?notebook如何實現打開數據集_python
- 2022-03-27 C語言中浮點數的精度丟失問題解決_C 語言
- 2023-02-09 Python去除html標簽的幾種方法總結_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支