教材:10710周志遠教授平行程式 https://www.youtube.com/playlist?list=PLS0SUwlYe8cxqw70UHOE5n4Lm-mXFXbZT 20250827 筆記 內容可能有錯僅供參考 8B~8D Synchronization Tools & Open Multi-Processing(OpenMP) 今日大綱 1.Semaphore 是什麼? 2.Monitors 是什麼? 3.OpenMP (Open Specification for Multi-Processing) 介紹 4.OpenMP 指令詳述 5. parallel 指令 6.工作分配建構子 (Work-Sharing Constructs) ### Semaphore 是什麼? - **定義與目的**: - Semaphore 主要是為了**解決同步化問題**。 - 與 Lock 不同,Semaphore 是一個 **counter**。 - 它代表在 Critical Section 裡面,到底有多少個執行緒 (thread) 可以進入,或是還有多少資源可以被存取。 - **與 Lock 的差異**: - **Lock** 是 **binary**,只能表示「有人在裡面」或「完全沒有人」,像是 0 或 1。 - **Semaphore** 可以是一個**大於一的數字**,因此可以控制多個執行緒同時存取資源。 - 如果將 Semaphore 的數量限制在 1,它就等同於 Lock,這種情況下我們稱之為 **Binary Semaphore**,但較少這樣使用,通常直接用 Lock 即可。 - 當數量大於 1 時,則稱為 **Counting Semaphore**。 - **操作方式**: - Semaphore 變數本身就是一個數字,你只能對它做**increment** 和 **decrement**。 - 當有執行緒使用或進入 Critical Section 時,這個數字會 **減一 (wait)**,表示可用的資源變少。 - 當有執行緒離開或釋放資源時,這個 **counter** 會 **加一 (signal)**,表示可用的資源變多。 - **對應函式 (API)**: - `sem_wait` (或 `p`): 用來獲取 (acquire) 資源,執行「減」的動作。如果計數器小於等於 0 (沒有資源),執行緒會被 **阻塞 (block)** 在此,直到有資源可用。 - `sem_post` (或 `signal`): 用來釋放 (release) 資源,執行「加」的動作。 - `sem_init`: 初始化 Semaphore 的值。這個初始值可以是任意大於零的整數,代表一開始有多少資源可用。 - `sem_destroy`: 銷毀 Semaphore。 - `sem_getvalue`: 取得目前計數器的值,得知還剩下多少資源. - **實作與使用**: - Semaphore 不包含在 `pthread.h` 裡面,而是由 **作業系統 (OS)** 直接提供,通常包含在 **POSIX API** (例如 `semaphore.h`) 中。 - 它主要用來同步化 **Process**,但也可以拿來同步化 **Thread。 - 其用法與 Condition Variable 類似,但功能不同。 - **使用者需要確保正確的邏輯**:例如,如果沒有使用資源卻呼叫了 `signal`,可用資源的計數會一直往上加,不會被初始值限制。如果連續呼叫兩次 Lock 但沒有 Unlock,也會造成問題。這表示這些工具雖然好用,但很容易因為程式設計錯誤導致難以偵測的 bug。 ### Monitors 是什麼? - **目的**: - Monitor 是一種 higher-level 的同步化工具,**簡化程式設計師的負擔**,讓他們不再需要手動編寫 `wait` 和 `signal` (或 Lock/Unlock) 等操作。 - **核心概念**: - Monitor 其實就是一個特殊的類別 。 - 在這個類別中,有 **私有變數 (private variables)** (即我們要保護的共享資料 shared data) 和定義存取這些資料的 **方法 (methods)**. - Monitor 的內建功能會保證在任何時間點,只有一個執行緒可以在這個類別內部執行它的方法 。這其實就是 Critical Section 的概念,但由 Monitor 自動處理。 - 只要是被 Monitor 類別包裝的資料,**絕對不會有同步化問題**,因為存取會自動序列化 。 - **Java 中的實作例子**: - Java 提供了 Monitor 的功能,主要有兩種方式: 1. **方法層級的 `synchronized` 關鍵字 (Method-level `synchronized`)**: - 在方法定義前面加上 `synchronized` 關鍵字,就能保證一次只有一個執行緒可以執行這個類別中任何被 `synchronized` 修飾的方法。 - 如果某些方法與共享資料無關,可以不加 `synchronized`,它們的行為就和一般的函式一樣。 2. **陳述句區塊的 `synchronized` 陳述句 (Statement-block level `synchronized`)**: - 你可以將一段程式碼包裝在 `synchronized (object)` 區塊中。 - `object` 是一個變數或記憶體參考 (memory reference),告訴編譯器這個記憶體內容可能有同步化問題。 - Java Runtime 會自動在執行時偵測並加上 Lock 和 Unlock 的陳述句,保護這個區塊內的記憶體存取。 - 這提供了更 **fine-grained** 的同步化控制。 - **優缺點**: - **優點**: 極大地減少了程式設計錯誤 ,因為同步邏輯由語言層級自動處理,簡化了複雜的偵錯過程。 - **缺點**: 相對來說,其執行效率可能較差,因為它可能會在內部加入一些不一定必要的 Lock,導致程式效能下降。Monitor 是一種高階模式,雖然易於描述,但內部實現可能較複雜。 ### OpenMP (Open Specification for Multi-Processing) 介紹 - **基本概念**: - OpenMP 是一種用於**共享記憶體 (shared memory) 程式**的函式庫,它透過 **編譯器指令 (compiler directives)** 的方式來實現平行化。 - 它的全名「Open Specification for Multi-Processing」代表著它是一個 open specification,只是一個介面,具體實作可能有多種版本。 - **不要與 OpenMPI 混淆**: OpenMPI 是一個用於訊息傳遞 (Message Passing Interface, MPI) 的庫,與 OpenMP 完全不同。 - OpenMP 是 **可移植的 (portable)**,因為它只是一個規範,程式碼在不同系統環境下只需重新編譯即可運行。 - **程式設計模型:Fork-and-Join**: - 這是 OpenMP 最核心且固定的模型。 - 在 OpenMP 程式中,一開始有一個主執行緒 (master thread)。 - 當遇到 OpenMP 的平行指令時,主執行緒會 fork 出多個執行緒 (類似 Pthread 的 `pthread_create`)。 - 這些被 fork 出的執行緒會執行同一段程式碼區塊。 - 當這段程式碼區塊執行完畢後,所有 fork 出的執行緒會 join 回主執行緒 (類似 Pthread 的 `pthread_join`)。 - 與 Pthread 相比,OpenMP 的彈性較小,因為它必須遵循 Fork-and-Join 模型,不能隨意在任何時間點建立任意的執行緒。但它在處理大部分平行化問題時非常方便。 - **編譯器指令 (Compiler Directives)**: - OpenMP 的主要工作方式是透過編譯器指令。程式設計師只需在程式碼中添加特定的 `#pragma omp` 指令。 - 編譯器會根據這些指令自動產生低階的平行程式碼 (類似 Pthread 程式碼)。 - OpenMP 幾乎沒有直接與程式執行邏輯相關的函式,大部分是查詢系統資訊的函式 。 - **指令語法結構**: - `#pragma omp directive_name [clause(parameter), ...]` - `#pragma omp`: 固定語法,讓編譯器辨識 OpenMP 指令。 - `directive_name`: 指定要使用的平行化方式 (例如 `parallel`, `do`,`for`)。 - `clauses`: 可選的參數設定,用來控制指令的行為。不同的指令會有不同的 clause 選項,例如 `if`, `num_threads`, `schedule`, `private`, `shared` 等。 - **資料範圍 (Data Scope)**: 這是編寫 OpenMP 程式時最棘手的地方之一。透過 `private`, `shared` 等 clause 來告訴編譯器變數是私有的 (local) 還是共享的 (global),編譯器會據此產生程式碼。 - **程式語言與大小寫**: - OpenMP 支援 C, C++, Fortran。 - OpenMP 指令是**大小寫敏感 (case-sensitive)** 的。 - 原則上一個指令中只能有一個 `directive_name` ( `parallel` 是唯一的特例,可以與其他工作分配指令結合)。 - 指令作用於一個 **陳述句區塊 (statement block)**,通常是一個用 `{}` 包起來的程式碼區塊。 ### OpenMP 指令詳述 #### `parallel` 指令 - **功能**: - **唯一會建立執行緒 (create threads) 的指令**。 - 建立一組執行緒 (a team of threads) - 這些建立的執行緒會執行**完全相同的**程式碼區塊,這個區塊稱為 **Parallel Region (平行區域)**。 - 在 Parallel Region 結束時,有一個**隱含的屏障 (implicit barrier)**。所有執行緒必須完成其工作才能進入下一個陳述句。這符合 Fork-and-Join 模型,執行緒會「結合」回主執行緒。 - 如果其中一個執行緒 crash,所有執行緒都會 crash,整個程式結束。 - **Parallel Region 的限制**: - 不能包含跳出 (branch out) 平行區域的跳轉指令 (例如 `goto`)。 - 不能跨越不同的檔案 (cross different files)。 - 可以呼叫其他函式,但不建議讓流程變得混亂。 - **決定執行緒數量 (Number of Threads)**: 依優先順序決定 1. **`if(condition)` clause**: - 如果 `condition` 為 `false`,則該平行區域會以**一個執行緒**執行,就好像 OpenMP 指令不存在一樣。 - 如果為 `true`,則繼續判斷後面的選項。 2. **`num_threads(N)` clause**: - 直接在 `#pragma omp parallel` 指令中指定要建立的執行緒數量 `N`。 - 這是最直接和明確的方式。 3. **`omp_set_num_threads(N)` API**: - 透過 OpenMP 函式呼叫設定一個**預設值 (default value)**。 - 必須在進入平行區域之前呼叫。 - 會影響所有後續未指定 `num_threads` 的平行區域。 4. **`OMP_NUM_THREADS` 環境變數 (Environment Variable)**: - 在系統環境中設定這個變數的值。 - 編譯器會在執行時偵測此變數來決定執行緒數量。 5. **系統 CPU 核心數**: - 如果以上都沒有設定,OpenMP 會使用目前系統的 CPU 核心數作為預設值。 - **巢狀平行 (Nested Parallelism)**: - 允許在一個平行區域內再包含另一個平行區域 (巢狀平行區域)。 - 巢狀平行是否被支援取決於 OpenMP 函式庫的實作和是否啟用 (`omp_set_nested()` 函式)。 - 如果巢狀功能被禁用,內層的平行區域會被忽略,並以一個執行緒執行。 ### 工作分配建構子 (Work-Sharing Constructs) - 這些指令的目的是**分配工作給已經存在的執行緒**,它們**不會建立新的執行緒**。 - 因此,它們**必須被包含在一個 `parallel` 區域內部**才有意義。通常你會看到 `parallel` 搭配這些指令,甚至簡寫成一個指令 (例如 `#pragma omp parallel for`)。 - 這些指令的末尾通常也有 implied barrier (隱含的屏障),所有被分配工作的執行緒必須完成才能進入下一個程式碼區塊 (除非使用 `nowait` clause)。 - 主要有三種類型: 1. **`#pragma omp for` (或 `do`)**: - **資料平行 (Data Parallelism)**: 專門用於將 `for` 迴圈的**迭代 (iterations)** 分配給不同的執行緒來平行執行。 - 前提是迴圈的迭代之間**不能有資料相依性 (data dependency)**。 - **常用 Clauses**: - **`nowait`**: 移除迴圈結束時的隱含屏障。 - 正常情況下,所有執行緒必須完成迴圈的所有迭代,才能一起進入下一個陳述句。 - 使用 `nowait` 後,一旦執行緒完成了它被分配的迭代,就可以立即繼續執行平行區域中的後續程式碼,無需等待其他執行緒。 - **`schedule(type [, chunk_size])`**: 定義迴圈迭代如何分配給執行緒。 - `type`: - **`static`**: 迭代在**運行前**被靜態分配。執行緒會以固定大小的區塊 (`chunk_size`) 輪流分配迭代。 - **`chunk_size`**: 每次分配給一個執行緒的迭代數量。 - 優點: 調度開銷 (scheduling overhead) 小。 - 缺點: 如果各迭代工作量不均,可能導致負載不平衡。 - **`dynamic`**: 迭代在**運行時**動態分配。執行緒完成當前工作後,會向調度器 (scheduler) 請求下一個工作區塊。 - 優點: 確保**負載高度平衡 (highly balanced)**,適合工作量不均的迭代。 - 缺點: 調度開銷較大,因為需要頻繁地請求工作。 - **`guided`**: `dynamic` 的變種。起始的 `chunk_size` 較大,隨著剩餘工作量的減少而逐步減小,最終可能減至 1。 - 優點: 兼顧負載平衡和減少調度開銷。 - **`runtime`**: 在**運行時**根據 `OMP_SCHEDULE` 環境變數的值來決定調度類型。 - **`auto`**: 由編譯器自動選擇調度類型。**不建議使用**,通常效果不佳。 - 選擇調度類型: 如果每個任務的工作量相似且可預期,選 `static`。如果工作量難以預期且有差異,選 `dynamic` 或 `guided`。 - **`ordered`**: 確保平行迴圈的結果以**循序執行 (sequential order)** 的順序產生。 - **不建議使用**,因為會引入大量的同步化機制 (synchronization locks),導致平行效率非常低。 - **`collapse(N)`**: 將巢狀 `for` 迴圈的 `N` 層扁平化 (flatten) 為一個單層迴圈,然後對其迭代進行調度。 - **增加平行度 (degree of parallelism)**。 - **限制**: 巢狀迴圈之間不能有任何陳述句。 - **注意**: 必須確保扁平化後的迭代之間**沒有資料相依性**,否則會導致錯誤的結果。 2. **`#pragma omp sections`**: - **任務平行 (Task Parallelism)**: 讓不同的執行緒執行**完全不同的程式碼區塊 (sections)** - 每個 `section` 會被一個執行緒執行。 - 你手動定義要分配給執行緒的特定程式碼段。 3. **`#pragma omp single`**: - 保證在該平行區域中,被此指令包裝的程式碼區塊**只會被一個執行緒執行**。 - 其他執行緒會等待此區塊完成 (因為有隱含屏障)。 - **應用場景**: 適合執行不適合平行化的任務,例如 **I/O 操作**,因為通常一個 process 或 thread 寫資料就足夠,避免競爭存取問題。 - **平行與工作分配指令的結合**: - `parallel` 指令可以與 `for`, `sections` 或 `single` 結合使用,例如 `#pragma omp parallel for`。 - 此時,`parallel` 的 clause (如 `num_threads`) 和工作分配指令的 clause (如 `schedule`) 會一起列在指令後面。 --- 其他課程連結 [平行程式1C~2B Introduction parallel programming](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/Syxh3H7Kxe) [平行程式3A~3D The Latest Developments and Applications Using Parallel Programming](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJh7QFVKle) [平行程式4A~4B IO Parallel IO and Program Analysis](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJLMsuHFgg) [平行程式5A~5B The Latest Developments and Applications Using Parallel Programming](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/SJh57hIFle) [平行程式6A~6B Communication Routines and Parallel Function Code](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/r1X9kX_Fle) [平行程式 6C~6D Communication Routines and Parallel Function Code](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/S1DPjoYFlx) [平行程式 7A~8A Pthread:Synchronization Problem & Tools](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJu-_0tKge) [平行程式 8B~8D Synchronization Tools & Open Multi-Processing(OpenMP)](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/H1ki4E2Fee) [平行程式 9A~9B Synchronization Construct](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BJTYMrpKlx) [平行程式 10A~10B Synchronization Tools & Open Multi-Processing Synchronization Construct](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/B1cY6M1qee) [平行程式 10C~10D Synchronization Tools & Open Multi-Processing Synchronization Construct](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BkgFaNg5gg) [平行程式 11A~11B Parallel Work Pool and Termination / Parallel Sorting](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/H1hfOw-5xl) [平行程式 12A~12B Parallel Sorting and Pipelined Computations](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/Symo-zQ9eg) [平行程式 12C~12D Parallel Sorting and Pipelined Computations](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BJYNKDVceg) [平行程式 13A-13B Sychronous Parallelism](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJ2UJ2Bqex) [平行程式 14A~14B Heterogeneous Computing](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BksS4yP5eg) [平行程式 14C~14D Heterogeneous Computing](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BJrfTUd9xx) [平行程式 15A~15B Parallel Programming Model on GPU](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/ByWnl-t5gg) [平行程式 16A~16B What is Compute Unified Device Architecture(CUDA)?](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HyYpsjcqgl) [平行程式 17A~18A 平行運算的CUDA](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/H1dUeBT5lg) [平行程式 18B~19A 記憶體層級 / CUDA的優化](https://hackmd.io/@JuitingChen/HyF44e1jge) [平行程式 19B~19D 記憶體層級 / CUDA的優化 ](https://hackmd.io/@JuitingChen/ryPEu4lieg) [平行程式 20A~20B CUDA優化全域和區域記憶體/共享記憶體](https://hackmd.io/@JuitingChen/r1X659Zoxl) [平行程式 21A~21B Parallel Reduction / Distributed Computing Framework](https://hackmd.io/@JuitingChen/HyiOpozjxl) [平行程式 NTHU-PP-Chap10-Big Data-Part1 ](https://hackmd.io/@JuitingChen/Hyc-e3Golx) [平行程式 NTHU-PP-Chap10-Big Data-Part2 ](https://hackmd.io/@JuitingChen/ryC_QTXoxl) [平行程式 NTHU-PP-Chap11-MapReduce](https://hackmd.io/@JuitingChen/HJgBXJOsge) [平行程式 NTHU-PP-Chap12-Distributed Training-Part1](https://hackmd.io/@JuitingChen/ryh5hBtsge) [平行程式 NTHU-PP-Chap12-Distributed Training-Part2](https://hackmd.io/@JuitingChen/rJ2G7kdjxg) [平行程式 NTHU-PP-Chap12-Distributed Training-Part3](https://hackmd.io/@JuitingChen/HkA471dilx) [平行程式 NTHU-PP-Chap13-UCX-Part1](https://hackmd.io/@JuitingChen/rJbq103ieg) [平行程式 NTHU-PP-Chap13-UCX-Part2](https://hackmd.io/@JuitingChen/SJpNmk_ixl) [平行程式 NTHU-PP-Chap13-UCX-Part3](https://hackmd.io/@JuitingChen/HkIUYa13xe)