原子地复制一个(或多个)位到一个整数

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

请注意,我在这里为 loadcompare_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();
}