原子地复制一个(或多个)位到一个整数
Atomically copy a bit (or bits) into an integer
我有一些代码通过首先在目标 int 中清除它们然后将它们 OR 运算到 int 中来将掩码位复制到整数中。
像这样:
bitsToSet = 6
targetInt &= ~(1 << bitsToSet)
targetInt |= desiredBitValue << bitsToSet
问题是它现在需要线程安全,我需要使操作原子化。我认为使用 std:atomic 只会使每个子操作成为原子操作而不是整个操作。
如何使整个操作(包括 &= 和 |= 操作)成为原子操作?
例如,如果我有一个像 SetBits(TARGET, MASK, VALUE)
这样的函数(或更好的宏),它会自动将 TARGET 中的 MASKed 位设置为 VALUE,这将为我解决问题。 MASK 和 VALUE 已经可以左移了。
我当前的非原子代码是
#define SetBits(TARGET, MASK, VALUE) {(TARGET) &= ~((uint64_t)MASK); (TARGET)|=((uint64_t)VALUE);}
可以在没有 CAS 的情况下以原子方式复制位,但要以条件代码为代价。实际上,可以使用原子 or 来设置位,而可以使用原子 and 来清除位。诀窍是将它们组合在一起:
bitToSet = 7;
// Assume targetInt is of type std::atomic<int>
if(desiredBitValue)
targetInt.fetch_or(1 << bitToSet);
else
targetInt.fetch_and(~(1 << bitToSet));
对于多位,到目前为止,在 C++ 中唯一可移植的方法是在循环中对 targetInt
使用 compare_exchange_weak
操作。
您可以使用比较交换循环:
void SetBitsAtomic(std::atomic<int>& target, int mask, int value) {
int original_value = target.load();
int new_value = original_value;
SetBits(new_value, mask, value);
while (!target.compare_exchange_weak(original_value, new_value)) {
// Another thread may have changed target between when we read
// it and when we tried to write to it, so try again with the
// updated value
new_value = original_value;
SetBits(new_value, mask, value);
}
}
这会读取 target
的原始值,执行屏蔽操作,然后将修改后的值写回 target
,前提是自读取后没有其他线程修改它。如果另一个线程修改了 target
,那么它的更新值将写入 original_value
并且它会一直尝试,直到它设法在其他任何人之前更新 target
。
请注意,我在这里为 load
和 compare_exchange_weak
操作使用了(默认)完全顺序一致性。您可能不需要完全的顺序一致性,但如果没有关于您使用它的用途的更多信息,我无法确切地知道您需要什么。
或者,您可以只使用互斥体:
std::mutex mtx;
void SetBitsAtomic(int& target, int mask, int value) {
std::lock_guard lock{mtx};
SetBits(target, mask, value);
}
这个 可能 的性能低于无锁 compare_exchange_weak
版本,但这同样取决于它的用途。它当然更简单、更容易推理,在您的情况下,这可能比原始性能更重要。
使用std::mutex
。您的代码应如下所示:
mutex m;
void SetBitsAtomic()
{
m.lock();
bitToSet = 7;
targetInt &= ~(1 << bitToSet);
targetInt |= desiredBitValue << bitToSet;
m.unlock();
}
我有一些代码通过首先在目标 int 中清除它们然后将它们 OR 运算到 int 中来将掩码位复制到整数中。
像这样:
bitsToSet = 6
targetInt &= ~(1 << bitsToSet)
targetInt |= desiredBitValue << bitsToSet
问题是它现在需要线程安全,我需要使操作原子化。我认为使用 std:atomic
如何使整个操作(包括 &= 和 |= 操作)成为原子操作?
例如,如果我有一个像 SetBits(TARGET, MASK, VALUE)
这样的函数(或更好的宏),它会自动将 TARGET 中的 MASKed 位设置为 VALUE,这将为我解决问题。 MASK 和 VALUE 已经可以左移了。
我当前的非原子代码是
#define SetBits(TARGET, MASK, VALUE) {(TARGET) &= ~((uint64_t)MASK); (TARGET)|=((uint64_t)VALUE);}
可以在没有 CAS 的情况下以原子方式复制位,但要以条件代码为代价。实际上,可以使用原子 or 来设置位,而可以使用原子 and 来清除位。诀窍是将它们组合在一起:
bitToSet = 7;
// Assume targetInt is of type std::atomic<int>
if(desiredBitValue)
targetInt.fetch_or(1 << bitToSet);
else
targetInt.fetch_and(~(1 << bitToSet));
对于多位,到目前为止,在 C++ 中唯一可移植的方法是在循环中对 targetInt
使用 compare_exchange_weak
操作。
您可以使用比较交换循环:
void SetBitsAtomic(std::atomic<int>& target, int mask, int value) {
int original_value = target.load();
int new_value = original_value;
SetBits(new_value, mask, value);
while (!target.compare_exchange_weak(original_value, new_value)) {
// Another thread may have changed target between when we read
// it and when we tried to write to it, so try again with the
// updated value
new_value = original_value;
SetBits(new_value, mask, value);
}
}
这会读取 target
的原始值,执行屏蔽操作,然后将修改后的值写回 target
,前提是自读取后没有其他线程修改它。如果另一个线程修改了 target
,那么它的更新值将写入 original_value
并且它会一直尝试,直到它设法在其他任何人之前更新 target
。
请注意,我在这里为 load
和 compare_exchange_weak
操作使用了(默认)完全顺序一致性。您可能不需要完全的顺序一致性,但如果没有关于您使用它的用途的更多信息,我无法确切地知道您需要什么。
或者,您可以只使用互斥体:
std::mutex mtx;
void SetBitsAtomic(int& target, int mask, int value) {
std::lock_guard lock{mtx};
SetBits(target, mask, value);
}
这个 可能 的性能低于无锁 compare_exchange_weak
版本,但这同样取决于它的用途。它当然更简单、更容易推理,在您的情况下,这可能比原始性能更重要。
使用std::mutex
。您的代码应如下所示:
mutex m;
void SetBitsAtomic()
{
m.lock();
bitToSet = 7;
targetInt &= ~(1 << bitToSet);
targetInt |= desiredBitValue << bitToSet;
m.unlock();
}