The only atomic type that is guaranteed to be lock-free is std::atomic_flag. All std::atomic<T> specializations may internally use mutexes.

Detailed description Link to heading

std::atomic_flag (not to be confused with std::atomic<bool>) is the only atomic type that is guaranteed by the C++ standard to be lock-free:

Operations on an object of type atomic_flag shall be lock-free.

Strictly speaking, the lock-free property means that if some threads are executing lock-free operations, at least one thread will eventually finish execution, regardless of the state of other threads. Therefore, if a thread holding a lock fails or is preempted, all threads waiting on that lock will not make any progress, which means that lock-free algorithms cannot use locking synchronization. Loosely speaking, when someone says “lock-free operation”, it most likely means “an operation that doesn’t use locks” (e.g., mutexes or spinlocks).

Note: A lock-free operation always has the looser obstruction-freedom guarantee, but not necessarily stricter wait-free guarantee.

The lock-free guarantee is provided only for std::atomic_flag. Other atomic types may not be lock-free, and their operations might be implemented with internal locking and thus might block. However, in practice, many lock-free std::atomic specializations will be available. To check whether a given std::atomic specialization is lock-free on the target platform, std::atomic<T>::is_lock_free() or the static constexpr std::atomic<T>::is_always_lock_free can be used. Why both? In theory, is_always_lock_free might be false while is_lock_free() is true, because of platform requirements, whose satisfaction might only be checked at runtime. For example, a platform might require atomics to be properly aligned to be lock-free, whereas a misaligned atomic object won’t provide lock-free operations.

Why is std::atomic_flag the only one with lock-free guarantees? N2427 states that:

Operations on an atomic_flag must be lock free. atomic_flag type is the minimum hardware-implemented type needed to conform to this standard. The remaining types can be emulated with atomic_flag, though with less than ideal properties.

Example Link to heading

For example, if we use a big enough type in the std::atomic<T> template, the CPU may not directly support atomics of this size, so, to provide atomicity, an implementation might fall back to locking:

#include <atomic>
#include <iostream>

int main() {
    struct A{int x[2];};
    struct B{int x[20];};
    std::cout << std::atomic<A>::is_always_lock_free <<
        std::atomic<B>::is_always_lock_free;
}

Possible output:

10