仅借助compare-and-swap原语能否实现多内存位置原子写入?
好问题!咱们直接切入核心:仅依靠标准的单变量CAS(Compare-and-Swap)原语,且不使用任何额外内存的话,无法实现对多个独立内存位置的原子写入。下面结合你的场景详细解释:
为什么单CAS做不到?
CAS的原子性仅覆盖单个内存位置的"读-比较-修改-写"全流程,它没有办法将多个内存位置的状态变化绑定成一个不可分割的原子操作。回到你的例子:
- 初始化
mem[0] = 0; mem[1] = 0 - 线程A要完成
mem[0] = 1; mem[1] = 1的原子写入 - 线程B读取并打印两个值,要避免出现
1,0或0,1的中间状态
如果只用单CAS操作,线程A只能分两次CAS分别更新mem[0]和mem[1]——不管先更新哪一个,两次操作之间都存在时间窗口,线程B完全可能在这个窗口内读取到一个已更新、一个未更新的中间状态。没有任何单CAS操作能让这两个写操作"同时"完成,因为CAS本身不支持跨位置的原子语义。
有没有变通的"伪原子"方案?
如果允许读写双方共同遵守一套协议,可以用CAS实现类似乐观锁的机制来避免看到中间状态,但这并非真正的原子写入:
比如约定mem[0]的临时值(比如2)表示写入正在进行:
- 线程A先CAS
mem[0]从0到2(标记写入开始) - 线程A写入
mem[1] = 1 - 线程A再CAS
mem[0]从2到1(标记写入完成) - 线程B读取时,若发现
mem[0]是2(临时值),就重试读取,直到拿到稳定值(0或1)
但这个方案有明显局限:
- 要求
mem[0]的合法值不能包含临时值2,通用性很差 - 本质是让读端主动规避中间状态,而非写入操作本身原子化
- 如果读端不遵守协议(直接裸读),还是会看到
2,0或2,1的中间状态
而且这种方案也没法支持任意数量的索引——每增加一个内存位置,要么需要额外的临时标记位(但你要求无额外内存),要么会让协议复杂度指数级上升,根本不现实。
对比你提到的信号量方案
你说信号量有两个问题:需要额外内存、无法支持任意索引。其实这也是所有多位置原子操作机制的共性:要协调多个位置的状态,必须有额外的控制状态存储(不管是信号量、锁、还是标记位);而支持任意索引的话,要么需要动态分配控制结构(还是额外内存),要么依赖硬件级别的多变量CAS指令(比如部分CPU支持的CAS2,能同时操作两个位置),但这已经超出了单变量CAS原语的范畴。
内容的提问来源于stack exchange,提问作者MaiaVictor

