C++11原子操作fetch_mult实现正确性及优化方案问询
关于std::atomic自定义fetch_mult实现的疑问解答
你的理解完全正确!第一个fetch_mult实现确实存在严重的多线程竞争问题,会导致线程陷入无限循环(属于“活锁”场景,而非严格意义上的死锁,但同样会让线程无法推进)。
为什么第一个实现会出问题?
我们再拆解一下你提到的场景:
- 线程T1执行
oldValue = shared.load(),拿到值5 - 线程T2也执行
oldValue = shared.load(),同样拿到5 - T2调用
compare_exchange_strong,此时shared的值还是5,CAS成功,把shared改成25 - 回到T1,它的
oldValue仍然是5,此时调用compare_exchange_strong时,shared的值已经是25,不等于5,CAS失败。但这个实现的循环里没有更新oldValue,所以下一次循环还是用旧的5去尝试CAS,永远无法匹配当前的25,线程会一直卡在循环里。
只有当其他线程把shared的值改回5,或者mult等于1(此时新值和旧值一样,CAS会成功),T1才能跳出循环,这显然不符合原子操作的预期。
你给出的第二个实现是正确的吗?
是的,这个实现是正确的!它采用了CAS操作的标准循环重试模式:
template <typename T> T fetch_mult(std::atomic<T>& shared, T mult){ while (true) { T oldValue = shared.load(); if (shared.compare_exchange_strong(oldValue, oldValue * mult)) return oldValue; } }
每次循环都会重新调用shared.load()获取当前最新的shared值作为oldValue,然后尝试CAS。如果CAS失败(说明其他线程修改了shared),下一次循环会拿到新的oldValue再重试,直到CAS成功为止,能正确处理多线程竞争的情况。
更高效的优化写法
其实std::compare_exchange_strong有一个很实用的特性:当CAS失败时,它会把当前shared的实际值写入第一个参数(也就是这里的oldValue)。利用这个特性,我们可以省去每次循环里的load()调用,让代码更高效:
template <typename T> T fetch_mult(std::atomic<T>& shared, T mult){ T oldValue = shared.load(); while (!shared.compare_exchange_strong(oldValue, oldValue * mult)) { // CAS失败时,oldValue已经被自动更新为shared当前的值,无需手动load } return oldValue; }
这个写法和你的第二个实现功能等价,但减少了不必要的load操作,性能更好,也是原子操作自定义扩展时的常用范式。
内容的提问来源于stack exchange,提问作者anderas
相关产品推荐
相关产品推荐

