Search code examples
c++bit-manipulationatomicbit

Atomically copy a bit (or bits) into an integer


I have some code that copies masked bits into an integer by first clearing them in the target int then ORing them into the int.

Like this:

bitsToSet = 6
targetInt &= ~(1 << bitsToSet)
targetInt |= desiredBitValue << bitsToSet

The problem is that it now needs to be thread safe, and I need to make the operation atomic. I think that using std:atomic<int> would only make each sub-operation atomic but not the operation as a whole.

How can I make the whole operation (including both the &= and the |= operations) atomic?

For example, it would solve the problem for me if I had a function (or better, a macro) like SetBits(TARGET, MASK, VALUE) that would atomically set the MASKed bits in TARGET to VALUE. The MASK and VALUE can already be left-shifted.

My current, non-atomic code is

#define SetBits(TARGET, MASK, VALUE) {(TARGET) &= ~((uint64_t)MASK); (TARGET)|=((uint64_t)VALUE);}

Solution

  • You could use a compare-exchange loop:

    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);
        }
    }
    

    This reads the original value of target, does the masking operations, and then writes the modified value back to target only if no other thread has modified it since it was read. If another thread has modified target then its updated value gets written into original_value and it keeps trying until it manages to update target before anyone else.

    Note that I've used (default) full sequential consistency here for the load and compare_exchange_weak operations. You may not need full sequential consistency, but I have no way of knowing exactly what you need without more info on what you're using this for.


    Alternatively, you could just use a mutex:

    std::mutex mtx;
    
    void SetBitsAtomic(int& target, int mask, int value) {
        std::lock_guard lock{mtx};
        SetBits(target, mask, value);
    }
    

    This may be less performant than the lock-free compare_exchange_weak version, but again it really depends what it's being used for. It's certainly simpler and easier to reason about, which may be more important than raw performance in your situation.