160 likes | 468 Views
資料結構與 C++ 程式設計 進階. 多執行緒( multi-thread )程式設計. 大綱. 前言 執行緒( thread ) 執行緒與平行程式設計( Parallel Programming ) 第一支多執行緒程式 使用 Event 旗標 同步化問題( Synchronization ) 使用 Critical Section. 前言. 程式 (Program) 儲存於硬碟中的可執行檔稱為 Program 行程 (Process) 載入記憶體中的可執行檔稱為 Process 執行緒 (Thread)
E N D
資料結構與C++程式設計進階 多執行緒(multi-thread)程式設計
大綱 • 前言 • 執行緒(thread) • 執行緒與平行程式設計(Parallel Programming) • 第一支多執行緒程式 • 使用Event旗標 • 同步化問題(Synchronization) • 使用Critical Section Page 2
前言 • 程式 (Program) • 儲存於硬碟中的可執行檔稱為 Program • 行程 (Process) • 載入記憶體中的可執行檔稱為 Process • 執行緒 (Thread) • Process中一段程式碼的執行序列稱為Thread,是作業系統能夠進行運算與排程的最小單位。 Page 3
執行緒(thread) • 簡介 • 執行緒(thread)是被包含在行程(Process)中實際運作的單位。 • 一個行程中可以並行多個執行緒,而這些執行緒共用同一份行程的系統資源、名稱位址等等。 Process 2 Process 1 signal, fd, global var.. etc signal, fd, global var.. etc Thread 3 Thread 1 Thread 3 Thread 1 Thread 2 Thread 2 Page 4
執行緒與平行處理 • 平行程式設計(Parallel Programming) • 將一項任務切成較小、並且可同時處理的子任務,在分配到多核心的處理器上以達到更高的效能。 • 單一執行緒與多執行緒程式比較 Process1 Thread1 Thread2 Time Time CPU0 CPU0 CPU1 CPU1 Page 5
第一支執行緒程式 • 使用_beginthread()產生執行緒 • _beginthread (執行緒要執行的函式, 堆疊大小, 變數位址) ; #include <process.h> // thread API #include <stdio.h> #include <windows.h> // Sleep() API void Thread( void* pParams ) { while(1) { printf("Thread and Data: %d\n", *(int *)pParams); Sleep(1000); } } int main () { int n = 5; _beginthread( Thread, 0, &n); while( 1 ) { printf("This is main() process.\n"); getchar(); } return 0; } Page 6
小練習01 • 呈上頁範例,試著使用getch()擷取鍵盤的+, -鍵,產生一個執行緒的使之可以產生一個時鐘的計數器。 • 參考範例:http://w.csie.org/~r97944012/train/p10-thread-timer.exe Page 7
一次產生多個執行緒 • 使用_beginthread()產生執行緒 • _beginthread (執行緒要執行的函式, 堆疊大小, 變數位址) ; #include <process.h> #include <stdlib.h> #include <stdio.h> #define N 20 typedefstruct _complex_data { intiVal; double fVal; } cData; // 重點一、多執行緒 // 重點二、執行緒的資料傳遞方法(傳指標) void Thread( void* pParams ) { cData *pData = (cData *)pParams; printf("%d %.2lf\n", pData->iVal, pData->fVal); } int main () { inti; cDatatData[N]; for(i = 0; i < N; i++) { tData[i].iVal = i; tData[i].fVal = i + 0.5; _beginthread( Thread, 0, &tData[i]); } printf("main()\n"); system("pause"); return 0; } Page 8
使用Event旗標 • 呈上頁範例,我們發現main()的主行程並不會等待所有的執行緒結束。 • 使用Event旗標等待Thread結束 • (1) 宣告旗標變數 • HANDLE 旗標變數; • (2) 初始化旗標 • CreateEvent(屬性 , 手動旗標, 初始值, 名稱) • (3) 設定旗標 • SetEvent(旗標); • (4) 等待信號 • WaitForSingleObject(旗標變數, 等待時間(ms或INFINITE)); • WaitForMultipleObjects( 陣列大小 , 旗標變數陣列, 等待所有旗標, 等待時間) Thread 1 Thread 2 Main process Page 9
等待多個執行緒才終止主行程 • 使用SetEvent() 搭配WaitForMultipleObjects() #include <process.h> #include <stdlib.h> #include <windows.h> #include <stdio.h> #define N 20 HANDLE hEvent[N]; typedefstruct _complex_data { inttID; intiVal; double fVal; } cData; void Thread( void* pParams ) { cData *pData = (cData *)pParams; printf("%d %.2lf\n", pData->iVal, pData->fVal); SetEvent(hEvent[pData->tID]); } int main () { inti; cDatatData[N]; for(i = 0; i < N; i++) { hEvent[i] = CreateEvent( NULL, FALSE, FALSE, NULL ); tData[i].tID = i; tData[i].iVal = i; tData[i].fVal = i + 0.5; _beginthread( Thread, 0, &tData[i]); } WaitForMultipleObjects(N , hEvent, TRUE, INFINITE); printf("main()\n"); system("pause"); return 0; } Page 10
小練習02 • 矩陣運算 • p11-matrix.cpp 是個可計算500 x 500的矩陣運算程式,試著建立2~N個thread將它進行平行計算,並試著估計一下優化的結果。 • (提示:若開啟4個thread,則將C矩陣拆成1/4運算。) A C B Page 11
執行緒的同步化問題範例 #include <process.h> #include <stdio.h> #define N 10000000 int x; void ThreadJob( void* pParams ) { inti; for ( i = 0; i < N; i++ ) x = x+1; } int main () { _beginthread( ThreadJob, 0, NULL ); _beginthread( ThreadJob, 0, NULL ); while( 1 ) { printf("x = %d\n", x); getchar(); } return 0; } Page 12
同步化問題(Synchronization) • 上一頁的範例,會產生一個race condition的問題。 • race condition定義 • 兩個以上的行程(或執行緒)共用同一資源時,因為共用同一個系統資源,在進行存取時會「因為執行的順序不同,導致結果不一致。」 • 通常,在多個行程同時進行「寫入」的動作時,才會發生race condition。舉例來說,有兩個行程同時想要將記憶體中的X變數加上一,有可能發生下列兩種情形: Process 1 Process 2 Process 1 Process 2 Read X = 1 • Add X with 1 Write X = 2 - - - - - - Read X = 2 Add X with 1 Write X = 3 Read X = 1 • Add X with 1 - - Write X = 2 - - - Read X = 1 Add X with 1 - Write X = 2 Page 13
同步化問題(Synchronization)之解決 • 要解決race condition導致執行緒之間的資料不同步的問題。通常我們會將執行緒對於記憶體的存取改為atomic operation:也就是「將讀取和寫入」某共享資源的動作,一次性的完成,並且期間不允許其他的行程打斷。 • 實作方式: • (1) 宣告旗標變數 • CRITICAL_SECTION cs; • (2) 初始化 • InitializeCriticalSection( &cs ); • (3) 將要設定為atomic operation的區域設為critical section • EnterCriticalSection( &cs ); • // …… atomic operation • LeaveCriticalSection( &cs ); Page 14
小練習03 • 試著將投影片第12頁的範例加上critical section,解決race condition的問題。 Page 15