You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CUDA多块全局锁原子操作失效问题及__threadfence()作用咨询

问题描述

环境配置

  • GPU:Quadro RTX 4000
  • CUDA版本:11.7

核函数与测试结果

实现1:floatAddLockExch(使用atomicExch释放锁)

__global__ void floatAddLockExch(float* addr, volatile int* lock) {
    bool goon = true;
    while (goon) {
        if (atomicCAS((int*)lock, 0, 1) == 0) {
            *addr += 1; // 临界区任务

            int lockValue = atomicExch((int*)lock, 0);
            if (lockValue != 1) {
                printf("Error in <%d, %d>\n", blockIdx.x, threadIdx.x);
            }
//
//          *lock = 0;
//          __threadfence();

            goon = false;
        }
    }
}

测试结果:

  • 以<<<1, 1024>>>启动时,*addr输出为1024;
  • 以<<<2, 1024>>>启动时,输出为1025,且无"Error..."输出。

实现2:floatAddLockFence(使用__threadfence())

__global__ void floatAddLockFence(float* addr, volatile int* lock) {
    bool goon = true;
    while (goon) {
        if (atomicCAS((int*)lock, 0, 1) == 0) {
            *addr += 1; // 临界区任务

//          int lockValue = atomicExch((int*)lock, 0);
//          if (lockValue != 1) {
//              printf("Error in <%d, %d>\n", blockIdx.x, threadIdx.x);
//          }

            *lock = 0;
            __threadfence();

            goon = false;
        }
    }
}

测试结果:

  • 以<<<1, 1024>>>启动时,*addr输出为1024;
  • 以<<<2, 1024>>>启动时,输出为2048。

疑问

按预期,全局变量lock的原子操作应跨所有块原子执行,为何floatAddLockExch在多块场景失效?__threadfence()是如何解决该问题的?


解答

1. floatAddLockExch多块场景失效的原因

CUDA的SM(流式多处理器)自带一级缓存,不同SM上的线程块访问全局内存时,会将数据缓存到本地。在floatAddLockExch中,虽然atomicCAS和atomicExch是原子操作,但临界区的*addr +=1操作结果可能还未同步到全局内存,其他SM上的线程块就通过原子操作获取了锁,读取到的addr是旧值,导致重复累加。

更关键的是:atomicExch释放锁后,当前线程块对addr的修改可能还停留在SM的缓存中,没有刷回全局内存。其他SM上的线程块拿到锁后,从全局内存或自身缓存读取addr,此时读到的是未更新的值,所以多次累加只生效了少数次数,最终结果远小于预期的2048。

另外,volatile修饰lock仅保证对lock的读写不被编译器优化,但无法保证跨SM的缓存一致性——不同SM的缓存不会自动同步,除非有内存栅栏或原子操作强制同步。

2. __threadfence()的作用

__threadfence()是CUDA的内存栅栏函数,它会强制当前线程块中所有未完成的内存操作(包括对addr的写操作)都刷回全局内存,并且确保这些操作对其他SM上的线程可见后,才执行栅栏之后的指令。

在floatAddLockFence中,先执行*lock =0释放锁,再调用__threadfence():

  • __threadfence()会确保临界区对addr的修改已完全刷入全局内存,同时lock=0的写操作也对所有SM可见;
  • 这样其他SM上的线程块通过atomicCAS获取锁时,既能看到lock已释放,也能读取到addr的最新值,从而正确执行累加操作。

需要注意的是:atomicExch仅保证对lock的操作是原子的,不会自动同步临界区的其他内存操作;而__threadfence()专门解决跨SM的内存可见性问题,确保临界区修改对所有线程可见后再释放锁。


内容的提问来源于stack exchange,提问作者user3059627

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 17:30:54