假设我有两个 int32 类型的原子变量,我可以选择将它们表示为
std::atomic<int64> both
并为我的第一个 in 保留前 32 位,为我的第二个 int 保留最后一个。
这在 x64 架构上看起来相当节省空间和时间,更不用说它允许各种黑魔法,因为人们可以抽象各种操作并使它们原子化:
first == a && second ==b
成为
both == ( int64(a) + int64(b) << 32 )
//Or some such... I'm not 100% sure this is correct but you get the idea
我发现这个技巧的一个问题是,我不太擅长在位级别进行操作,而 C++ 在位级别操作方面并不是很友好,特别是当你尝试完成更复杂的操作或将两个以上的变量(例如两个数字和几个布尔值)打包到同一个原子中。
所以我想知道是否有一个标准化的方法来应用这种技巧。一种模式甚至标准功能,在看到时很容易被其他编码人员识别并且更易于实现者使用?同样,这种模式是否足够有用以保证这种标准化,或者与它可能带来的可能的烦恼和 UB 相比,它的用处是否很快就变得过时了?
使用原子来解决“先读后写”的方法是使用循环:
void setBit(atomic<int64_t>& bitset, int bit)
{
int64_t val = 1LL << bit;
int64_t prev = bitset;
while ((!(bitset & val)) &&
!bitset.compare_exchange_weak(prev, (prev | val))
;
}
您可以扩展此方法来创建通用的按位运算函数
如果您不想处理移位和位掩码操作,您可以将值转换为具有适当大小的指针,并使用简单的指针算术或数组索引表示法来访问/存储您需要的值
现代 C++ 爱好者会对此犹豫不决,但以下演示了这两种方法是等效的
在此示例中,我将 4 x int8 值打包到一个 int32 中
int32_t packed_values = 0x01020304; // packed values [4, 3, 2, 1]
// pointer cast + array dereference approach
int8_t *unpacked_values = (int8_t *)&packed_values;
printf("packed value 0 == %d", unpacked_values[0]);
printf("packed value 1 == %d", unpacked_values[1]);
printf("packed value 2 == %d", unpacked_values[2]);
printf("packed value 3 == %d", unpacked_values[3]);
// shift mask approach
int8_t value0 = (packed_values & 0x000000FF);
int8_t value1 = (packed_values & 0x0000FF00) >> 8;
int8_t value2 = (packed_values & 0x00FF0000) >> 16;
int8_t value3 = (packed_values & 0xFF000000) >> 24;
printf("shift/mast value 0 == %d", value0);
printf("shift/mast value 1 == %d", value1);
printf("shift/mast value 2 == %d", value2);
printf("shift/mast value 3 == %d", value3);
assert (value0 == unpacked_values[0]);
assert (value1 == unpacked_values[1]);
assert (value2 == unpacked_values[2]);
assert (value3 == unpacked_values[3]);
如果您的目标架构不同,请注意字节序