std :: atomic将一对原子int32视为一个原子int64?

时间:2012-01-13 22:55:21

标签: c++ c++11 lock-free

我有一对unsigned int32

std::atomic<u32> _start;
std::atomic<u32> _end;

有时我想用比较交换设置开始或结束,所以我不希望在整个64位对上使用CAS可能导致虚假失败。我只想使用32位CAS。

_end.compare_exchange_strong(old_end, new_end);

现在我可以将start和end作为一个原子64位读取。或两个独立的32位读取。进行一次64位原子获取(编译器添加适当的内存栅栏)而不是两个独立的32位原子位读取和两个内存栅栏(或者编译器会优化它吗?)

如果是这样,我将如何在c ++ 11中做到这一点?

2 个答案:

答案 0 :(得分:3)

该标准不保证std::atomics与基础类型具有相同的大小,也不保证atomic上的操作是无锁的(尽管它们可能适用于uint32最小)。因此,我很确定没有任何符合标准的方法将它们组合成一个64bit原子操作。因此,您需要决定是否要将两个变量手动组合成64位变量(并且只能使用64位操作)。

作为示例,平台可能不支持64bit CAS(对于第一个Pentium IIRC添加的x86,因此在编译486兼容时不可用。在这种情况下,它需要以某种方式锁定,所以原子可能包含64bit变量和锁定。

关于围栏:这取决于您为操作指定的memory_order。如果内存顺序指定两个操作需要按照它们被执行的顺序可见,编译器显然将无法优化栅栏,否则可能。当然假设你只针对x86 memory_order_seq_cst实际上会从我记忆中发出一个屏障指令,所以更少的东西会阻碍编译器完成指令重新排序,但不会有实际的惩罚。)

当然,根据您的平台,您可以通过std::atomic<int32>int64将两个union作为reinterpret_cast之一进行投射,从而获得豁免,标准不需要此行为,并且(至少在理论上)可以随时停止工作(新的编译器版本,不同的优化设置,......)

答案 1 :(得分:0)

如果您的两个整数需要原子更新,那么您必须将它们视为单个原子64位值,您真的没有其他选择。单独的整数更新不是原子的,也不可行。我同意Unions在这里并不相关,并建议你只需将这对整数转换为(INT64)并执行你的Cas64。

使用关键部分是过度杀伤 - 使用Cas64,它们只需要大约33个机器周期(未选择),而关键部分的成本更像是100个未安装的周期。

请注意,在版本化指针上执行此完全相同的操作是很常见的,32位指针由32位指针和32位指针组成,它们一起更新为一个,如上所述使用Cas64。此外,它们实际上必须“正确排列”,因为您从不希望这些值超出缓存行边界。