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.
std::atomic_is_lock_free
and std::atomic::is_lock_free
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
See also: