"Lock" here is in the sense of "mutex", not specifically in reference to the x86 instruction prefix named lock
.
A trivial and generic way to implement std::atomic<T>
for arbitrary types T
would be as a class containing a T
member together with a std::mutex
, which is locked and unlocked around every operation on the object (load, store, exchange, fetch_add, etc). Those operations can then be done in any old way, and need not use atomic machine instructions, because the lock protects them. This implementation would be not lock free.
A downside of such an implementation, besides being slow in general, is that if two threads try to operate on the object at the same time, one of them will have to wait for the lock, which may actually block and cause it to be scheduled out for a while. Or, if a thread gets scheduled out while holding the lock, every other thread that wants to operate on the object will have to wait for the first thread to get scheduled back in and complete its work first.
So it is desirable if the machine supports truly atomic operations on T
: a single instruction or sequence that other threads cannot interfere with, and which will not block other threads if interrupted (or perhaps cannot be interrupted at all). If for some type T
the library has been able to specialize std::atomic<T>
with such an implementation, then that is what we mean by saying it is lock free. (It is just confusing on x86 because the atomic instructions used for such implementations are named lock
. On other architectures they might be called something else, e.g. ARM64's ldxr/stxr
exclusive load/store instructions.)
The C++ standard allows for types to be "sometimes lock free": maybe it is not known at compile time whether std::atomic<T>
will be lock-free, because it depends on special machine features that will be detected at runtime. It's even possible that some objects of type std::atomic<T>
are lock-free and others are not. That's why atomic_is_lock_free
is a function and not a constant. It checks whether this particular object is lock-free on this particular day.
However, it might be the case for some implementations that certain types can be guaranteed, at compile time, to always be lock free. That's what is_always_lock_free
is used to indicate, and note that it's a constexpr bool
instead of a function.
std::atomic::is_always_lock_free
being true really mean? – Despicablestd::atomic_is_lock_free()
and my question is forstd::atomic<T>::is_always_lock_free
. Former a function call return an integer [-1, 1]. Latter is justbool
ean value. – Despicablestd::atomic<uint32_t>::is_always_lock_free
is true whilestd::atomic_is_lock_free(&value)
is 1, which meansfor the built-in atomic types that are sometimes lock-free
. Sounds like a conflict to me. – Despicablestd::atomic_is_lock_free(&value)
returnsbool
, which when true just means that this particular object is lock-free at the moment; it doesn't distinguish between "always" and "sometimes". You might be thinking of theATOMIC_XXX_LOCK_FREE
macros; ifstd::atomic<T>::is_always_lock_free
is true thenATOMIC_T_LOCK_FREE
ought to have the value 2, though I'm not sure if this is technically required. It should be harmless for an implementation to promise less than it actually provides. – Fluter