多线程并发临界区退出:普通赋值与原子操作的合理性判断
临界区退出:普通赋值
gf=0 vs 原子操作InterlockedExchange 先直接给结论:必须使用InterlockedExchange(&gf, 0),绝对不能用普通的gf=0,核心原因涉及内存可见性和指令重排序两个关键问题,下面一步步拆解:
1. 别被原子性误导:这不是核心问题
在x86架构下,对齐的64位普通赋值(对应汇编mov qword ptr [gf], 0)本身是原子的——不会出现写一半被其他线程打断的情况。但原子性只是锁机制的基础要求,我们还要解决更关键的内存一致性问题。
2. 内存屏障是关键:保证临界区操作的可见性
当你用InterlockedCompareExchange(带lock前缀)抢占锁时,lock前缀会触发全内存屏障:
- 阻止CPU将临界区内部的内存操作重排到
lock cmpxchg指令之后(保证进入锁之前的操作不会跑到临界区里) - 强制把缓存中的修改同步到主存,让其他线程能看到当前线程的操作状态
如果退出时用普通的gf=0,这个赋值没有lock前缀,也就没有内存屏障:
- CPU可能会将临界区里的内存写操作重排到
gf=0之后——也就是说,其他线程看到gf变为0、进入临界区时,上一个线程在临界区里的修改可能还没同步到主存,直接引发数据竞争 - 普通写操作的修改可能只停留在当前CPU的缓存中,其他线程的
InterlockedCompareExchange可能读取到旧的缓存值,误以为锁还没释放,出现不必要的等待甚至极端场景下的死锁
3. 语义一致性:锁的获取和释放要配对
从锁的设计语义上来说,用原子操作获取锁,就应该用原子操作释放锁。InterlockedExchange带lock前缀,和InterlockedCompareExchange的内存语义完全匹配,能保证整个锁机制的内存一致性。
举个汇编层面的直观对比:
- 普通赋值
gf=0对应的汇编:mov qword ptr [gf], 0(无lock,无内存屏障) InterlockedExchange(&gf, 0)对应的汇编:lock xchg qword ptr [gf], rax(带lock,触发内存屏障)
只有后者能确保临界区的所有操作都完成并同步到主存后,才释放锁,让其他线程安全进入。
内容的提问来源于stack exchange,提问作者ross martin
相关产品推荐
相关产品推荐

