本页详细讨论了 CAS 回圈:https : //preshing.com/20150402/you-can-do-any-kind-of-atomic-read-modify-write-operation/
fetch_multiply
C 中的一个示例:
uint32_t fetch_multiply(std::atomic<uint32_t>& shared, uint32_t multiplier){
uint32_t oldValue = shared.load();
while (!shared.compare_exchange_weak(oldValue, oldValue * multiplier)){}
return oldValue;
}
本质上,如果*memory
值与我们的 oldValue 匹配,则 anewValue
会自动存盘,否则 oldValue 会更新为*memory
.
我有两个问题:
1 - 为什么我们必须检查oldValue
存储器中是否仍然没有变化?如果我们只是将 newValue 写入存储器会发生什么?我们是否试图避免覆写或使用来自另一个执行绪的中间值?
2-假设这个场景有 2 个执行绪:
- 执行绪 B 试图以非原子方式存盘未对齐的值。发生存盘撕裂。
- 执行绪 A 尝试交换。
- 交换失败,因为 oldValue 不匹配。存储器中的中间(撕裂)值被加载到我们的 oldValue。
- 执行绪 A 与一个中间值相乘并尝试另一个成功的交换。
- 现在执行绪 B 将其剩余的值写入相同的位置,部分覆写我们之前的写入。
我假设Thread B
可以以这幺多的延迟运行,如果是这样,我们不仅乘以一个中间值,它甚至还被部分覆写,而 CAS 什么也没做。
uj5u.com热心网友回复:
我设法说服自己代码是错误的。我认为它应该是这样的:
uint32_t fetch_multiply(std::atomic<uint32_t>& shared, uint32_t multiplier){
uint32_t oldValue;
do {
oldValue = shared.load();
} while (!shared.compare_exchange_weak(oldValue, oldValue * multiplier));
return oldValue;
}
如果目标值更改为其他值,我们需要再次读取 oldValue ,否则我们将永远旋转。
但是,CAS 构造的要点是您永远无法在共享位置观察到中间值。眼泪是不可能的;shared.load()
防止它。这是在硬件中实作的。
“如果我们只是将 newValue 写入存储器会发生什么?” 那么你没有原子访问权限。始终遵循模式。
“非对齐值”如果shared
是非对齐的,您甚至在谈论std::atomic
. 非对齐指标不能安全地取消参考。对于正常情况,*
您只是依赖于字节可寻址架构,但这是一个std::atomic
. 如果它没有对齐,即使在 x86 上也可能出现故障。
0 评论