跟我学C++中级篇—标准库中的CAS接口分析

📅 发布时间:2026/7/12 21:02:18 👁️ 浏览次数:
跟我学C++中级篇—标准库中的CAS接口分析
一、从无锁编程谈起在前面的多线程编程时提到过CAS这种无锁编程的情况。无锁编程的一个重要的前提是标准库中其实系统底层也提供了类似的机制的原子操作类型它也是硬件基础的比较交换指令的向上延伸。而要想实现CASC标准库中提供了两个接口函数即compare_exchange_strong和 compare_exchange_weak。也就是说在不同的平台或不同的语言中CAS的接口和实现机制是略有不同这一点大家要明白。二、强弱比较交换compare_exchange_strong和 compare_exchange_weak很好理解从字面上讲强比较交换是严格的比较不会出现伪失败spurious failure性能稍差一些而弱比较交换则相对宽容一些有可能出现伪失败但性能稍微强一些。从设计角度来看这也非常必要的。不同的情况要有不同的方法来匹配而不能以一敌千生拉硬配。二者的区别在于强比较交换只有在比较真正不同的情况下才会失败而弱比较交换则有可能在比较相同的情况也会失败。先看一下在C中的定义boolcompare_exchange_weak(Texpected,T desired,std::memory_order success,std::memory_order failure);这里有一个比较让英文一般人费解的地方先看一下这个函数的意义比较当前的原子变量值是否等于预期值由调用者提供。如果相等则将原子变量的值设置为一个新的值由调用者提供。如果不相等则将当前的原子变量值写入预期值并返回 false。这里面有一个预期值可在上面的参数中expected和desired都是期望的意思那上面的说法是不是有点让人费解这两个变量哪个是描述中的预期值呢在这个接口的说明中其实就可以看出来expected - pointer to the value expected to be found in the atomic object desired - the value to store in the atomic object if it is as expected而单纯从英文的含意来看expected是一种客观的预测一种合理的期望即它是一种目标而desired强调主观的愿望是一种期望的结果。再和上面的英文一匹配就明白了expected是调用者提供的期望值而desired是符合预期情况下的存储结果。虽然不同的平台有着细微的不同但一般来说软件层面上会对其进行“透明性”的屏蔽不会被上层应用人员感知另外一个就是硬件上目前开发者接触的环境主要还是以X86平台为主。ARM主要在手机平台上而这种情况下用到CAS的机会要相当小。在X86平台上提供了CMPXCHG指令这个指令是一种原子操作。所谓原子操作就是必然会执行完成。那么这也意味着在底层的实现机制上它们二者没有多大不同。大家这时会不会想起X86上的内存序对也是这样的问题。以X86平台上基本可以不用考虑内存序默认使用强内存序的问题。这是不是说内存序也在影响着CAS指令呢确实如此。常见的几种内存序与编译器及处理器性能优化对应的情况如下memory_order_seq_cst全局顺序一致要求最严影响也最小 memory_order_acq_rel获取-释放语义要求一般影响中等 memory_order_relaxed仅保证原子性要求最低所以影响也最大在AMD平台上由于采用了LL/SCLoad-Linked/Store-Conditional机制而且其内存序也采用了弱内存序的情况则由于指令处理机制的并行行为有可能导致伪失败。也就是说这是“胎里带”再强大的软件也搞不定。一般来说在非循环或严格要求情况下推荐使用compare_exchange_strong反之则使用compare_exchange_weak。正常情况下在X86平台上二者几乎没有区别但如果是在ARM等平台上则需要考虑伪失败的情况出于性能考虑使用compare_exchange_weak。三、伪失败和ABA以及活锁在CAS中经常遇到的ABA问题在前面分析过多核和多CPU编程——ABA的问题有兴趣可回看。另外就是活锁这个在前面也详细分析过死锁和活锁。此处重点分析一下伪失败问题。所谓伪失败spurious failure就是没有失败。没有失败的情况下应该赋新值但伪失败也被当成失败那么就没有达到预想的结果即没有失败也当成了失败。伪失败是compare_exchange_weak的典型特征用来和compare_exchange_strong显式的区分开这玩意儿有点布隆过滤器的味道啊。怎么来理解这种情况呢其实很简单就是老话“人有失手马有失蹄”。伪失败出现的原因在于硬件基础环境即上面提到的AMD这类LL/SC的架构平台它们包括PowerPC、MIPS以及RISC_V等软件上的缓存一致性问题哪里都有它中断等异常行为引起的陷阱行为多线程访问冲突类似于1中的上层软件映射其实从上面的分析可看出来伪失败基本与硬件关系紧密。象这种X86平台的就不用在考虑了。四、应用和例程再看一下cppreference提供的例程#includeatomic#includeiostreamstd::atomicintai;inttst_val4;intnew_val5;bool exchangedfalse;voidvalsout(){std::coutai ai tst_val tst_val new_val new_val exchanged std::boolalphaexchanged\n;}intmain(){ai3;valsout();// tst_val ! ai tst_val is modifiedexchangedai.compare_exchange_strong(tst_val,new_val);valsout();// tst_val ai ai is modifiedexchangedai.compare_exchange_strong(tst_val,new_val);valsout();return0;}运行结果ai 3 tst_val 4 new_val 5 exchanged false ai 3 tst_val 3 new_val 5 exchanged false ai 5 tst_val 3 new_val 5 exchanged true其实有兴趣的可以多看一下CAS相关的例程这个在GITHUB和其它网站上有很多这里就不再重复贴相关的代码了。五、总结一个技术从不同的角度去分析会有全新的认知。正如盲人摸象每个人都明白哂然一笑。但其每个人在实际的工作特别是技术开发中经常犯这种错误。而且随着阅历的增加和技术水平的不断深入和扩展会明白自己的局限性。这也是哲学意义上的波浪式前进螺旋式上升。