__threadfence函数是memory fence函数,用来保证线程间数据通信的可靠性。与同步函数不同,memory fence不能保证所有线程运行到同一位置,只保证执行memory fence函数的线程生产的数据能够安全地被其他线程消费。
(1)__threadfence:一个线程调用__threadfence后,该线程在该语句前对全局存储器或共享存储器的访问已经全部完成,执行结果对grid中的所有线程可见。
(2)__threadfence_block:一个线程调用__threadfence_block后,该线程在该语句前对全局存储器或者共享存储器的访问已经全部完成,执行结果对block中的所有线程可见。
上面是官方解释,但是看完之后还是不明其所以然,尤其是手册中给的元素求和代码。为了明白其真正函数,自己把代码实现了一遍,然后通过运行明白了__threadfence的含义与作用。
__device__ int count=0; __global__ static void sum(int* data_gpu,int* block_gpu,int *sum_gpu,int length) { extern __shared__ int blocksum[]; __shared__ int islast; int offset; const int tid=threadIdx.x; const int bid=blockIdx.x; blocksum[tid]=0; for(int i=bid*THREAD_NUM+tid;i<length;i+=BLOCK_NUM*THREAD_NUM) { blocksum[tid]+=data_gpu[i]; } __syncthreads(); offset=THREAD_NUM/2; while(offset>0) { if(tid<offset) { blocksum[tid]+=blocksum[tid+offset]; } offset>>=1; __syncthreads(); } if(tid==0) { block_gpu[bid]=blocksum[0]; __threadfence(); int value=atomicAdd(&count,1); islast=(value==gridDim.x-1); } __syncthreads(); if(islast) { if(tid==0) { int s=0; for(int i=0;i<BLOCK_NUM;i++) { s+=block_gpu[i]; } *sum_gpu=s; } } }
上述CUDA代码实现了block之间对元素求和,关键的地方在32行和34行。起初自己认为__threadfence或者原子操作单独都可以完成运算,所以我通过分别去除__threadfence和后面的原子操作来验证结果的正确性,结果发现:
- 单独的__threadfence不能给出正确结果;
- 只用原子操作可以给出正确结果。
一开始对结果很奇怪,然后从网上搜各种解释,得到的结论是:threadfence不是保证所有线程都完成同一操作,而只保证正在进行fence的线程本身的操作能够对所有线程安全可见fence不要求线程运行到同一指令,而barrier有要求。上述结论指出__threadfence函数不是同步函数,如果单纯地让block 0去计算最终的结果,这时可能会存在还有其他block尚未执行,这时得到的结果必然是错误的。
虽然只用原子操作可以给出正确结果,但是也不能保证在其他情况下也是正确的(GPU编程需要特别注意当前条件下正确的程序换个条件不一定正确,反映了GPU编程的复杂性)。