From C++ Concurrency in Action:
difference between std::atomic and std::atomic_flag is that std::atomic may not be lock-free; the implementation may have to acquire a mutex internally in order to ensure the atomicity of the operations
I wonder why. If atomic_flag is guaranteed to be lock-free, why isn't it guaranteed for atomic<bool> as well?
Is this because of the member function compare_exchange_weak? I know that some machines lack a single compare-and-exchange instruction, is that the reason?
First of all, you are perfectly allowed to have something like std::atomic<very_nontrivial_large_structure>, so std::atomic as such cannot generally be guaranteed to be lock-free (although most specializations for trivial types like bool or int probably could, on most systems). But that is somewhat unrelated.
The exact reasoning why atomic_flag and nothing else must be lock-free is given in the Note in N2427/29.3:
Hence the operations must be address-free. No other type requires lock-free operations, and hence the 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.
In other words, it's the minimum thing that must be guaranteed on every platform, so it's possible to implement the standard correctly.
The standard does not garantee atomic objects are lock-free. On a platform that doesn't provide lock-free atomic operations for a type T, std::atomic<T> objects may be implemented using a mutex, which wouldn't be lock-free. In that case, any containers using these objects in their implementation would not be lock-free either.
The standard provide an opportunity to check if an std::atomic<T> variable is lock-free: you can use var.is_lock_free() or atomic_is_lock_free(&var). For basic types such as int, there is also macros provided (e.g. ATOMIC_INT_LOCK_FREE) which specify if lock-free atomic access to that type is available.
std::atomic_flag is an atomic boolean type. Almost always for boolean type it's not needed to use mutex or another way for synchronization.
Related
From C++ Concurrency in Action:
difference between std::atomic and std::atomic_flag is that std::atomic may not be lock-free; the implementation may have to acquire a mutex internally in order to ensure the atomicity of the operations
I wonder why. If atomic_flag is guaranteed to be lock-free, why isn't it guaranteed for atomic<bool> as well?
Is this because of the member function compare_exchange_weak? I know that some machines lack a single compare-and-exchange instruction, is that the reason?
First of all, you are perfectly allowed to have something like std::atomic<very_nontrivial_large_structure>, so std::atomic as such cannot generally be guaranteed to be lock-free (although most specializations for trivial types like bool or int probably could, on most systems). But that is somewhat unrelated.
The exact reasoning why atomic_flag and nothing else must be lock-free is given in the Note in N2427/29.3:
Hence the operations must be address-free. No other type requires lock-free operations, and hence the 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.
In other words, it's the minimum thing that must be guaranteed on every platform, so it's possible to implement the standard correctly.
The standard does not garantee atomic objects are lock-free. On a platform that doesn't provide lock-free atomic operations for a type T, std::atomic<T> objects may be implemented using a mutex, which wouldn't be lock-free. In that case, any containers using these objects in their implementation would not be lock-free either.
The standard provide an opportunity to check if an std::atomic<T> variable is lock-free: you can use var.is_lock_free() or atomic_is_lock_free(&var). For basic types such as int, there is also macros provided (e.g. ATOMIC_INT_LOCK_FREE) which specify if lock-free atomic access to that type is available.
std::atomic_flag is an atomic boolean type. Almost always for boolean type it's not needed to use mutex or another way for synchronization.
Looking at std::atomic and it's default specializations I read:
These specializations have standard layout, trivial default constructors, and trivial destructors.
I also read for is_lock_free:
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, e.g. if only aligned memory accesses are naturally atomic
on a given architecture, misaligned objects of the same type have to
use locks.
Now here's the catch that I don't get:
How can any atomic type where the Standard prescribes trivial ctor/dtor ever be using any kind of mutex -- all mutexes I ever came across required non-trivial initialization.
This leads to the following questions:
Do major platforms provide any locking operation (like a mutex) that is "initalization free" per object. (That would be the "other locking operations".)
Is there any known implementation today for default std::atomic specializations that isn't lock free (and still fulfills the trivial ctor/dtor requirement)?
Am I simply confusing something here? :-)
It seems to me that even the simplest spin lock (see atomic_flag) needs non-trivial initialization, so I fail to see how this could be implemented.
Disclaimer: Purely out of academic interest, as this kinda jumped out on me while reading these docs.
Here is a possible solution: if an atomic operation uses a lock but has a trivial constructor and destructor, the mutex may be a global mutex shared between many atomic values.
This, I believe, is the case that the standard authors were allowing. It is possible to use trivial constructors and destructors for mutexes with static duration on some common platforms (such as POSIX).
// This is copied plain C here, not C++
// So nothing fancy
#include <pthread.h>
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
If the std::atomic default constructors were permitted to be non-trivial, then it would be difficult to use them during initialization.
std::atomic<int> my_flag;
Because my_flaghas a trivial constructor, it is static-initialized. Static initialization happens before dynamic initialization. So you can be sure that all the global std::atomic variables are initialized before your constructors run.
What is the reason for why is_lock_free requires an instance (it's a member function)? Why not a metafunction of the type, or a static constexpr member function?
I am looking for an actual instance of why it is necessary.
The standard allows a type to be sometimes lock-free.
section 29.4 Lock-free property
The ATOMIC_..._LOCK_FREE macros indicate the lock-free property of the
corresponding atomic types, with the signed and unsigned variants
grouped together. The properties also apply to the corresponding
(partial) specializations of the atomic template. A value of 0
indicates that the types are never lock-free. A value of 1 indicates
that the types are sometimes lock-free. A value of 2 indicates that
the types are always lock-free.
The C++ atomic paper n2427 states the reason behind:
... The proposal provides run-time lock-free query functions rather
than compile-time constants because subsequent implementations of a
platform may upgrade locking operations with lock-free operations, so
it is common for systems to abstract such facilities behind dynamic
libraries, and we wish to leave that possiblility open. Furthermore,
we recommend that implementations without hardware atomic support use
that technique. ...
And also (as Jesse Good pointed out):
The proposal provides lock-free query functions on individual objects rather than whole types to permit unavoidably misaligned atomic variables without penalizing the performance of aligned atomic variables
Of course, there's no such thing in std, but I need equivalent functionality.
I have a lock-free data structure templated on a type T, where T is provided by the user, and what I need to statically assert is that T is a type that is atomically assignable on x86 or x86-64 (which includes all built-in integral constants and floating point types, and any typedef thereof, but I think is not necessarily limited to those). I'm guessing that merely checking that the type is trivially assignable and that its sizeof is <= 8 is not sufficient. What's the best way to do this? Forcing T to be an std::atomic<> and then checking is_lock_free() is out of the question.
"atomically assignable" is not sufficient condition for using a type to implement a lock-free data structure, so this idea is going down to the wrong path from the start.
Using std::atomic (and friends) is the only way in C++ to have both the atomicity and the ordering guarantees necessary to implement a lock-free data structure. Atomic assignment is useless if no other thread will ever see it.
What is the reason for why is_lock_free requires an instance (it's a member function)? Why not a metafunction of the type, or a static constexpr member function?
I am looking for an actual instance of why it is necessary.
The standard allows a type to be sometimes lock-free.
section 29.4 Lock-free property
The ATOMIC_..._LOCK_FREE macros indicate the lock-free property of the
corresponding atomic types, with the signed and unsigned variants
grouped together. The properties also apply to the corresponding
(partial) specializations of the atomic template. A value of 0
indicates that the types are never lock-free. A value of 1 indicates
that the types are sometimes lock-free. A value of 2 indicates that
the types are always lock-free.
The C++ atomic paper n2427 states the reason behind:
... The proposal provides run-time lock-free query functions rather
than compile-time constants because subsequent implementations of a
platform may upgrade locking operations with lock-free operations, so
it is common for systems to abstract such facilities behind dynamic
libraries, and we wish to leave that possiblility open. Furthermore,
we recommend that implementations without hardware atomic support use
that technique. ...
And also (as Jesse Good pointed out):
The proposal provides lock-free query functions on individual objects rather than whole types to permit unavoidably misaligned atomic variables without penalizing the performance of aligned atomic variables