前言
在並發,多線程環境下,同步是一個很重要的環節。同步即是指進程/線程之間的執行順序約定。
本文將介紹如何通過共享內存機制實現塊內多線程之間的同步。
至於塊之間的同步,需要使用到 global memory,代價較為高昂,目前使用的情況也不多,就先不介紹了。
塊內同步函數:__syncthreads ()
線程調用此函數后,該線程所屬塊中的所有線程均運行到這個調用點后才會繼續往下運行。
代碼示例
使用同步思想優化之前一篇博文中提到的數組求和程序。在新的程序中,讓每個塊中的第一個線程將塊中所有線程的運算結果都加起來,然后再存入到結果數組中。這樣,結果數組的長度與塊數相等 (原來是和總線程數相等),大大降低了 CPU 端程序求和的工作量以及需要傳遞進/出顯存的數據 (代碼下方如果出現紅色波浪線無視之):
1 // 相關 CUDA 庫 2 #include "cuda_runtime.h" 3 #include "cuda.h" 4 #include "device_launch_parameters.h" 5 6 // 此頭文件包含 __syncthreads ()函數 7 #include "device_functions.h" 8 9 #include <iostream> 10 #include <cstdlib> 11 12 using namespace std; 13 14 const int N = 100; 15 16 // 塊數 17 const int BLOCK_data = 3; 18 // 各塊中的線程數 19 const int THREAD_data = 10; 20 21 // CUDA初始化函數 22 bool InitCUDA() 23 { 24 int deviceCount; 25 26 // 獲取顯示設備數 27 cudaGetDeviceCount (&deviceCount); 28 29 if (deviceCount == 0) 30 { 31 cout << "找不到設備" << endl; 32 return EXIT_FAILURE; 33 } 34 35 int i; 36 for (i=0; i<deviceCount; i++) 37 { 38 cudaDeviceProp prop; 39 if (cudaGetDeviceProperties(&prop,i)==cudaSuccess) // 獲取設備屬性 40 { 41 if (prop.major>=1) //cuda計算能力 42 { 43 break; 44 } 45 } 46 } 47 48 if (i==deviceCount) 49 { 50 cout << "找不到支持 CUDA 計算的設備" << endl; 51 return EXIT_FAILURE; 52 } 53 54 cudaSetDevice(i); // 選定使用的顯示設備 55 56 return EXIT_SUCCESS; 57 } 58 59 // 此函數在主機端調用,設備端執行。 60 __global__ 61 static void Sum (int *data,int *result) 62 { 63 // 聲明共享內存 (數組) 64 extern __shared__ int shared[]; 65 // 取得線程號 66 const int tid = threadIdx.x; 67 // 獲得塊號 68 const int bid = blockIdx.x; 69 70 shared[tid] = 0; 71 // 有點像網格計算的思路 72 for (int i=bid*THREAD_data+tid; i<N; i+=BLOCK_data*THREAD_data) 73 { 74 shared[tid] += data[i]; 75 } 76 77 // 塊內線程同步函數 78 __syncthreads (); 79 80 // 每個塊內索引為 0 的線程對其組內所有線程的求和結果再次求和 81 if (tid == 0) { 82 for(int i = 1; i < THREAD_data; i++) { 83 shared[0] += shared[i]; 84 } 85 // result 數組存放各個塊的計算結果 86 result[bid] = shared[0]; 87 } 88 } 89 90 int main () 91 { 92 // 初始化 CUDA 編譯環境 93 if (InitCUDA()) { 94 return EXIT_FAILURE; 95 } 96 cout << "成功建立 CUDA 計算環境" << endl << endl; 97 98 // 建立,初始化,打印測試數組 99 int *data = new int [N]; 100 cout << "測試矩陣: " << endl; 101 for (int i=0; i<N; i++) 102 { 103 data[i] = rand()%10; 104 cout << data[i] << " "; 105 if ((i+1)%10 == 0) cout << endl; 106 } 107 cout << endl; 108 109 int *gpudata, *result; 110 111 // 在顯存中為計算對象開辟空間 112 cudaMalloc ((void**)&gpudata, sizeof(int)*N); 113 // 在顯存中為結果對象開辟空間 114 cudaMalloc ((void**)&result, sizeof(int)*BLOCK_data); 115 116 // 將數組數據傳輸進顯存 117 cudaMemcpy (gpudata, data, sizeof(int)*N, cudaMemcpyHostToDevice); 118 // 調用 kernel 函數 - 此函數可以根據顯存地址以及自身的塊號,線程號處理數據。 119 Sum<<<BLOCK_data,THREAD_data,THREAD_data*sizeof (int)>>> (gpudata,result); 120 121 // 在內存中為計算對象開辟空間 122 int *sumArray = new int[BLOCK_data]; 123 // 從顯存獲取處理的結果 124 cudaMemcpy (sumArray, result, sizeof(int)*BLOCK_data, cudaMemcpyDeviceToHost); 125 126 // 釋放顯存 127 cudaFree (gpudata); 128 cudaFree (result); 129 130 // 計算 GPU 每個塊計算出來和的總和 131 int final_sum=0; 132 for (int i=0; i<BLOCK_data; i++) 133 { 134 final_sum += sumArray[i]; 135 } 136 137 cout << "GPU 求和結果為: " << final_sum << endl; 138 139 // 使用 CPU 對矩陣進行求和並將結果對照 140 final_sum = 0; 141 for (int i=0; i<N; i++) 142 { 143 final_sum += data[i]; 144 } 145 cout << "CPU 求和結果為: " << final_sum << endl; 146 147 getchar(); 148 149 return 0; 150 }
運行結果
PS:矩陣元素是隨機生成的
小結
共享內存,或者說這個共享數組是 CUDA 中實現同步最常用的方法。