I have a problem in which I need to be able to atomically update TWO uint64_t
's simultaneously. It's easy enough to write each one of them atomically (e.g. have two std::atomic<uint64_t>
's), but that would still lead to a situation where one is updated but the other is not. It's also easily possible with a lock and a mutex.
But I want to write both atomically, without any kind of lock, so that I can still have member variables that are of type uint64_t
so that there's no locking on read. This is because my use-case involves reading them lots and lots and lots of times, but writing them exceedingly rarely (~reading 1x/ms, writing 1x/5 min). Is it possible? If so, how?
For std::atomic
the standard says that (emphasis mine)
The primary
std::atomic
template may be instantiated with any TriviallyCopyable type T:struct Counters { int a; int b; }; // user-defined trivially-copyable type std::atomic<Counters> cnt; // specialization for the user-defined type
So you can just create a struct of 2 uint64_t
like this
struct atomic128 {
uint64_t a1, a2;
};
which is trivially copyable (it's easy to confirm with std::is_trivially_copyable
), and then use std::atomic<atomic128>
. You'll get an error if std::atomic<type>
is not trivially copyable
That way the compiler will automatically use lock-free updating mechanism if it's available. No need to do anything special, just check that with either of the below if necessary
All atomic types except for std::atomic_flag may be implemented using mutexes or other locking operations, rather than using the lock-free atomic CPU instructions. Atomic types are also allowed to be sometimes lock-free: for example, if only some subarchitectures support lock-free atomic access for a given type (such as the CMPXCHG16B instruction on x86-64), whether atomics are lock-free may not be known until runtime.
Here's a demo on Compiler Explorer. As you can see a lock cmpxchg16b
is emitted, although GCC 7 and up will just call __atomic_store_16
which internally use cmpxchg16b
if it's available
On some platforms long double
is a 128-bit type or is padded to 128 bits, therefore std::atomic<long double>
may be another solution, but of course you need to check its size and whether it's lock-free or not first
Another alternative is Boost.Atomic. It also has the macros BOOST_ATOMIC_INT128_LOCK_FREE
and BOOST_ATOMIC_LONG_DOUBLE_LOCK_FREE
to check
On some CPUs 128-bit SSE operations are also atomic, unfortunately there's no way to check whether you can use that or not