Single bit manipulations with guaranteed atomicity
Asked Answered
A

3

6

Is there a way to set, clear, test and flip a single bit as an atomic operation in c++? For example bitwise variants to "compare_and_swap".

Algar answered 26/6, 2017 at 0:9 Comment(5)
std::atomic_bool?Knelt
There is no "C/C++". Pick one.Fromma
What architecture are you working with? If a given architecture has no support for bit level atomic operations then the language/compiler will not magically make it possible.Reserpine
@Tony K the architecture is x86-64Algar
check out std::bitset when using c++Edra
D
8

Manipulating bits atomically requires a compare_exchange RMW to avoid touching other bits in the atomic variable. Testing a bit is not a modifying operation, therefore a load() suffices.

You will have to add range error checking

template<typename T, typename OP>
T manipulate_bit(std::atomic<T> &a, unsigned n, OP bit_op)
{
    static_assert(std::is_integral<T>::value, "atomic type not integral");

    T val = a.load();
    while (!a.compare_exchange_weak(val, bit_op(val, n)));

    return val;
}

auto set_bit = [](auto val, unsigned n) { return val | (1 << n); };
auto clr_bit = [](auto val, unsigned n) { return val & ~(1 << n); };
auto tgl_bit = [](auto val, unsigned n) { return val ^ (1 << n); };


int main()
{
    std::atomic<int> a{0x2216};

    manipulate_bit(a, 3, set_bit);  // set bit 3

    manipulate_bit(a, 7, tgl_bit);  // toggle bit 7

    manipulate_bit(a, 13, clr_bit);  // clear bit 13

    bool isset = (a.load() >> 5) & 1;  // testing bit 5
}
Developer answered 26/6, 2017 at 1:31 Comment(9)
Good; I would return val;, as knowing what you transitioned from is often useful.Aubervilliers
Agree; it's actually closer to the design conventions that atomic operations followDeveloper
Am I missing something or shouldn't you update val within the loop? Otherwise you have a deadlock until a has its old value.Thinker
@morty If compare_exchange_weak returns false, it updates its first argument with the current value. For that reason, the first argument (val), has to be an lvalue reference.Developer
@LWimsey: Ah yes. Thanks. I really whish C++ would require a keyword to highlight that it's a lvalue reference and that it might be modified.Thinker
why not use fetch_or, fetch_and or fetch_xor ?Copernicus
@deltanine: you're correct, the 1ULL<<n or T(1)<<n part isn't part of the atomic operation, so fetch_and/or/xor are better ways to implement this. Hopefully a compiler for x86 would turn a.fetch_or(1<<n) into lock bts, if the return value is only tested at bit-position n. Or into bts reg, reg / lock or if the return value is unused. Otherwise the compiler will use a lock cmpxchg retry loop for you, but it's still better to use a single fetch_op in the source since it can compile efficiently for other ISAs, especially ARMv8.1Dripps
I made a terrible idea based on thisKain
I made a much better idea based on this It now supports modifying multiple members of a bitfield, each of which can be multiple bits themselves, with conditional expressions. Very handy!Kain
C
5

To set a bit atomically in a std::atomic<int> a or std::atomic<unsigned int> a,
use a.fetch_or(bit) (also |= is overloaded, but that won't let you use an order weaker than seq_cst)

To atomically clear a bit in an atomic integer, you can use a.fetch_and(~bit) (also &=)

To atomically flip a bit in an atomic integer, you can use a.fetch_xor(bit) (also ^=)

There are also non-member-function versions of these, like std::atomic_fetch_or(&a, bit), but in ISO C++ it's not safe to point a std::atomic_int* at an int object.


To do atomic operations on a plain int variable, use C++20 std::atomic_ref auto foo_atomic = std::atomic_ref<int>( foo ) and do foo_atomic.fetch_or(bit). This is only safe if no other threads are reading or writing the underlying object, only via their own atomic_ref objects. Also, be sure to declare the underlying int with alignas(std::atomic_ref<int>::required_alignment), especially for integer types wider than int.

Copernicus answered 22/2, 2023 at 2:8 Comment(6)
These are not atomic operations when discussing multithreadingKain
Please elaborate.Copernicus
When you perform these sort of operations in one CPU, the C++ language offers no guarantee that the other CPUs will see the modified value.Kain
Only bitwise atomic operations? What about the other std::atomic operations?Copernicus
No operation is atomic on basic int and bools, even bitwise operations. std::atomic<int> and std::atomic<bool> are atomic, but the question and answer don't directly mention those.Kain
@MooingDuck: The question doesn't say they need to do this on a plain int. If they do, that's what C++20 std::atomic_ref is for (as long as no other threads can be accessing the plain int while you do this.) Updated this answer to make that clearer.Dripps
W
4

Flipping a bit in an integer is just a compare and exchange operation. That you're using it to test and flip a single bit doesn't change anything. So a simple compare_exchange_weak loop will do this.

Whitewall answered 26/6, 2017 at 0:17 Comment(4)
in compare_exchange_weak it is assumed that you know the expected result. This is not the case in bit manipulations.Algar
Note that C/C++ "atomics" is not the same as atomic operation in the sense of being uninterruptible, it simply guarantees a certain level of thread safety. For example stdatomic.h would not be suitable in kernel code interruptible by ISRs. See #19901024Reserpine
@chook can't you do a read first to determine the expected result?Progestin
@chook If you know what the operation is, you can determine the expected result for any operation, bit operation or otherwise. You can use compare_exchange_weak to turn any non-atomic transformation of a value to an atomic one.Whitewall

© 2022 - 2024 — McMap. All rights reserved.