CUDA线程协作之共享存储器“__shared__”&&“__syncthreads()”

在GPU并行编程中,一般情况下,各个处理器都需要了解其他处理器的执行状态,在各个并行副本之间进行通信和协作,这涉及到不同线程间的通信机制和并行执行线程的同步机制。

共享内存“__share__”


CUDA中的线程协作主要是通过共享内存实现的。使用关键字“__share__”声明共享变量,将使这个变量驻留在共享内存中,该变量具有以下特征:

  • 位于线程块的共享存储器空间中
  • 与线程块具有相同的生命周期
  • 仅可通过块内的所有线程访问

对于GPU上启动的每个线程块,CUDA C编译器都将创建该变量的一个副本。 线程块中的每个线程都共享这块内存,但线程却无法看到也不能修改其他线程块的变量副本。 这就使得一个线程块中的多个线程能够在计算上进行通信和协作。而且,共享内存缓冲区驻留在物理GPU上,在访问共享内存时的延迟要远远低于访问普通缓冲区的延迟,使得共享内存的访问非常高效。

线程同步机制“__syncthreads()”


关键字“__share__”只是声明了共享变量,位于同一个线程块中的不同线程都可以访问该变量,如果没有同步机制,将会发生竞态条件 (Race Condition),导致错误的运行结果。
CUDA确保同步的方法是调用“__syncthreads()”。__syncthreads()将确保线程块中的每个线程都执行完 __syncthreads()前面的语句后,才会执行下一条语句。

以下是CUDA和OpenCV的应用中,绘制一幅图像,Grid的尺寸大小是60*60,Block的尺寸大小是10*10,在各个线程块内声明了一个共享变量sharedMem:

[cpp] view plain copy
  1. #include "cuda_runtime.h"      
  2. #include <highgui.hpp>      
  3.   
  4. using namespace cv;  
  5.   
  6. #define DIM 600   //图像长宽  
  7. #define PI 3.1415926535897932f    
  8.   
  9. __global__ void kernel(unsigned char *ptr)  
  10. {  
  11.     // map from blockIdx to pixel position      
  12.     int x = threadIdx.x + blockIdx.x * blockDim.x;  
  13.     int y = threadIdx.y + blockIdx.y * blockDim.y;  
  14.     int offset = x + y * blockDim.x * gridDim.x;  
  15.   
  16.     __shared__  float sharedMem[16][16];  
  17.     const float period = 128.0f;  
  18.     sharedMem[threadIdx.x][threadIdx.y] =  
  19.         255 * (sinf(x*2.0f*PI / period) + 1.0f) *  
  20.         (sinf(y*2.0f*PI / period) + 1.0f) / 4.0f;  
  21.     __syncthreads();  
  22.   
  23.     ptr[offset * 3 + 0] = 0;  
  24.     ptr[offset * 3 + 1] = sharedMem[15 - threadIdx.x][15 - threadIdx.y];  
  25.     ptr[offset * 3 + 2] = 0;  
  26. }  
  27.   
  28. // globals needed by the update routine      
  29. struct DataBlock  
  30. {  
  31.     unsigned char   *dev_bitmap;  
  32. };  
  33.   
  34. int main(void)  
  35. {  
  36.     DataBlock   data;  
  37.     cudaError_t error;  
  38.   
  39.     Mat image = Mat(DIM, DIM, CV_8UC3, Scalar::all(0));  
  40.   
  41.     data.dev_bitmap = image.data;  
  42.     unsigned char    *dev_bitmap;  
  43.   
  44.     error = cudaMalloc((void**)&dev_bitmap, 3 * image.cols*image.rows);  
  45.     data.dev_bitmap = dev_bitmap;  
  46.   
  47.     dim3    grid(DIM / 10, DIM / 10);  
  48.     dim3   block(10, 10);  
  49.     //DIM*DIM个线程块    
  50.     kernel << <grid, block >> > (dev_bitmap);  
  51.   
  52.     error = cudaMemcpy(image.data, dev_bitmap,  
  53.         3 * image.cols*image.rows,  
  54.         cudaMemcpyDeviceToHost);  
  55.   
  56.     error = cudaFree(dev_bitmap);  
  57.   
  58.     imshow("__share__ and __syncthreads()", image);  
  59.     waitKey();  
  60. }


如果线程间不加入__syncthreads()同步机制,同一线程块内不同线程访问sharedMem,获取的结果可能是不一样的,生成的图像如下,有散乱的杂点:

CUDA线程协作之共享存储器“__shared__”&amp;&amp;“__syncthreads()”

加入__syncthreads()同步机制,保证了同一线程块中不同的线程都执行完成__syncthreads()这个集合点之前的部分之后,才继续往下执行,所以不同的线程访问sharedMem获取的结果是一致的,图像无杂散点,是一个规律的排布:

CUDA线程协作之共享存储器“__shared__”&amp;&amp;“__syncthreads()”