Atomics and volatile solve different problems, though. Atomics ensure a read or write completes uninterrupted. Volatile ensures that a read or write is never optimised away.
I think C11 atomics can be optimised away (for example, reading a value twice in a row might result in only a single actual read).
Happy to be corrected, though.
Yep, that's why atomics are only an alternative to uses of volatile _for atomicity_. For the original use case of accessing hardware registers, volatile is still the correct choice.
It is indeed possible for C11 atomics to be optimized, although interestingly, the three major compilers do very little such optimization. This paper [1] lists some optimizations that are implemented in LLVM and some that aren't; it's from 2015 but from some quick testing it seems like not much has changed since.
[1] https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n44...
If optimizing repeated atomic loads is indeed allowed, waiting for a signal by spinning on an atomic load could loop forever. Yet I have the feeling most people consider such code to be valid. Are they wrong?
x=1
x=2
Can be changed to just x=2. Still forward progress and eventual visibility must be guaranteed.In that way, atomic complete volatile instead of being orthogonal o it, because it provides the ordering semantic missing in volatile. And it doesn't replace it completely, because as you said, atomic accesses can still be optimized away.
So in most use-cases of volatile, you actually want to declare your variables atomic+volatile along with the correct memory_order on your atomic operations.
In C11 any type of any size can have an atomic qualifyer. That means you can have a 50 byte struct that is an atomic. No hardware has a 50 byte atomic instruction so that is not implementable using atomics. The standard gets around this by letting an implementation have a hidden mutex to guarantee that the operations will be atomic.
The problem with this is Windows. Windows lets an application load dynamicaly and shared libraries (DLL). This breaks the C11 Atomic model. Let me illustrate using an example:
Application A creates an atomic data structure, and the implementation creates a mutex for it. Application B does the same thing. Application A wants to share this data structute with dll X. It then has to share its mutex with the DLL sop that the DLL and application uses the same syncronization primitive. Now Application B wants to do the same thing, problem is DLL X cant use Application Bs Mutex, becaus it is required to use Apllication As mutex.
C11s Atomics will never be implemented on Windows because they cant be! Besides, all major compilers do support intrinsic atomics using volatile, that are nearly identical, (and in some ways better understood) so thats what I recomend using. Linus has indicated that he thinks the C11 concurrent memory model is broken so the kernel will continiue to use volatile and intrincics.
volotile gives you some decired propperties when multi-threadding, it is observable and therfor order rependent, but it does not have release/aquire semantics and it is not required to syncronize changes to other processors, so you need to use atomic intrincics in conjunction with volatile types. Volatile alone is NOT enough to be thread safe.
volatile is only ordered with other volatile calls and is otherwise UB when combined with non-atomics. Whereas an atomic is well-defined for ordering on other operations too. There are even some operations (eg seq-cst on a set of memory locations) which are known to be incorrectly executed in certain scenarios if modeled with volatile+fence, and require the use of atomics.
Even on hardware where loads and srtores are atomic (x86), you still need the atomic ops to manage ordering.
There is also fork on posix systems, which is incompatible with using atomics in the child process for basically this reason though of accidentally partially sharing a loaded library between two processes. Most libc documentation will state that only async-safe calls are permitted after fork until exec for this reason.
This means that you can use DLLs as a mechanism to communicate between multiple applications.
[1] of course even on unix you can mmap state on demand if you want to share between processes, but it is absolutely not the default.
Indeed the problem is otherwise not restricted just to memory sharing: even the particular CPU instructions chosen can mean one compiler is incompatible with the output of a different compiler when it comes to atomics even when locks are not involved (the specifics of which barriers are used and where often mean there are multiple valid, but mutually incompatible ways, to emit atomic instructions)
They are implementing them though. See https://devblogs.microsoft.com/cppblog/c11-atomics-in-visual...
The post is still accurate, but in 2011 C and C++ added atomics, which are a more portable alternative to uses of volatile for atomicity. They can be more efficient in some cases than the locks suggested by the post, especially in CPUs with higher core counts. (Note that dual-core consumer CPUs were around by 2010 but had only existed for a few years. Linux only finished removing the Big Kernel Lock in 2011.)