Why doesn't std::atomic initialisation do atomic release so other threads can see the initialised value? Why doesn't std::atomic initialisation do atomic release so other threads can see the initialised value? multithreading multithreading

Why doesn't std::atomic initialisation do atomic release so other threads can see the initialised value?


It's because the converting constructor is constexpr, and constexpr functions can't have side effects such as atomic semantics.

In DR846, Alastair Meredith writes:

I'm not sure if the initialization is implied by use of constexpr keyword (which restricts the form of a constructor) but even if that is the case, I think it is worth spelling out explicitly as the inference would be far too subtle in that case.

The resolution for that defect (by Lawrence Crowl) was to document the constructor with the note:

[Note: Construction is not atomic. —end note]

The note was then expanded to the current wording, giving an example of a possible memory race (via memory_order_relaxed operations communicating the address of the atomic) in DR1478.

The reason that the converting constructor needs to be constexpr is (primarily) to allow static initialization. In DR768 we see:

Further discussion: why is the ctor labeled "constexpr"? Lawrence [Crowl] said this permits the object to be statically initialized, and that's important because otherwise there would be a race condition on initialization.

So: making the constructor constexpr eliminates race conditions on static-lifetime objects, at the cost of a race in dynamic-lifetime objects that only occurs in fairly contrived situations, since for a race to occur the memory location of the dynamic-lifetime atomic object must be communicated to another thread in a way that does not result in the value of the atomic object being also synchronized to that thread.


That is an intentional design choice (there's even a note in the standard warning about it) and I think it was done in an attempt to be compatible with C.

The C++11 atomics were designed so that they could be used by WG14 for C as well, using the non-member functions such as atomic_load with types such as atomic_int rather than the member functions of the C++-only std::atomic<int>. In the original design, the atomic_int type has no special properties and atomicity is only achieved through atomic_load() and other functions. In that model atomic_init is not an atomic operation, it just initializes a POD. Only a subsequent atomic_store(&i, 1) call would be atomic.

In the end, WG14 decided to do things differently, adding the _Atomic specifier which makes the atomic_int type have magic properties. I'm not sure whether that means initialization of C atomics could be atomic (as it stands, atomic_init in C11 and C++11 is documented to be non-atomic), so maybe the C++11 rule is unnecessary. I suspect people will argue that there are good performance reason to keep initialization non-atomic, as interjay's comment above says, you need to send some notification to the other thread that the obejct is constructed and ready to be read from, so that notification could introduce the necessary fencing. Doing it once for the std::atomic initialization and then a second time to say the object is constructed could be wasteful.


I'd say, it's because a construction is never a thread communication operation: When you construct an object, you fill formerly uninitialized memory with sensible values. There is no way for another thread to tell whether that operation has finished unless it is explicitely communicated by the constructing thread. If you race with construction anyway, you immediately have undefined behavior.

Since the creating thread must explicitly publish its success on constructing a value before another thread can be allowed to use it, there is simply no point in synchronizing constructors.