3

If a variable is declared with the std::atomic template, such as std::atomic<int>, is it guaranteed that access via the methods in std::atomic will result in a consistent value (that is, one written by a write through the std::atomic methods) regardless of ordering?

As far as I know, this equivalent to asking whether reads or writes can be "torn" - that is, written or real in multiple parts visible at the ISA level.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • 3
    See [the documentation for `std::atomic`](http://en.cppreference.com/w/cpp/atomic/atomic). It answers all of your questions. If you have a problem understanding some part of that documentation, you need to explain exactly what part of the description you're not sure about. Learning how to read technical documentation is a required skill for every C++ developer. – Sam Varshavchik Aug 31 '17 at 02:45
  • This is not a real answer, but regardless of `std::atomic`, aligned writes that are the size of a pointer or smaller are atomic on all the major ISAs these day (x86/x86_64, ARM, AArch64). It's usually not sufficient for multithreaded scenarios, but for sure, bolting on `std::atomic` won't make matters worse. – zneak Aug 31 '17 at 04:49
  • @SamVarshavchik - I scanned that document at some length, but I didn't see any reference to atomicity specifically. – BeeOnRope Sep 01 '17 at 18:50
  • Did you see the "well defined" part, in the very first paragraph, with the link to the memory_order specification, that goes, in excruciating detail, what happens to all the reads and writes? – Sam Varshavchik Sep 01 '17 at 21:55
  • @SamVarshavchik - yes, I did. I also read the linked page, and it did have this note: _When a thread reads a value from a memory location, it may see the initial value, the value written in the same thread, or the value written in another thread._ However, this clearly isn't true for arbitrary reads and writes (which could be torn), and it doesn't restrict such access to `std::atomic` so it left me confused (i.e., the claim seems to be _too strong_ at least by itself). Finally, that text doesn't seem to appear in the standard, so I guess it was just invented by `cppreference`? – BeeOnRope Sep 01 '17 at 21:59
  • The restrictions imposed by various models prevent tears, by definition. In order to "tear" around an update you have to reorder around it, by definition. – Sam Varshavchik Sep 01 '17 at 22:01
  • @SamVarshavchik - I don't think you can invoke "non-reordering" to show that no tearing occurs, since for example `mo_relaxed` allows essentially any ordering! So perhaps you just have to rely on the definition of "atomically" as Peter does in his answer, or perhaps the standard (which I do not have a copy of) has some other language which guarantees it (i.e., some kind of guarantee that reads on `std::atomic` will always read either the initial value or some written value, and not anything else - no "out of thin air" values). – BeeOnRope Sep 01 '17 at 22:09
  • I never claimed that all memory models prevent tearing. Individual atomic methods take a parameter that sets that operation's model. If the model prohibits reordering that would result in a tear, then there's no tearing. If it doesn't, then there may be. This is not complicated. – Sam Varshavchik Sep 01 '17 at 22:12
  • @SamVarshavchik - now it seems like it is getting complicated (to be fair, I never heard anyone try to claim that memory model issues are anything but simultaneously complicated and unintuitive)! So is there _any_ model that allows reordering that would result in a tear? I'm still at a loss for how reordering is related to tearing: I think you can, for example, allow arbitrary unconstrained reordering but still prohibit tearing (and that's exactly what `std::memory_order_relaxed` seems to do). – BeeOnRope Sep 01 '17 at 22:19

1 Answers1

5

Atomic, from the Greek atom meaning indivisible, is synonymous with "no tearing". It means that the entire operation happens indivisbly. Everything you can do with a std::atomic type is always atomic (no tearing).

C++14 draft N4140 section 29.3 Order and consistency is the first part of that chapter that gets down to details. One of the very first points is (1.4):

[ Note: Atomic operations specifying memory_order_relaxed are relaxed with respect to memory ordering. Implementations must still guarantee that any given atomic access to a particular atomic object be indivisible with respect to all other atomic accesses to that object. — end note ]

As far as technical language that lays out the atomicity requirement, every operation (like .store(), .load(), .fetch_add()) is defined with language like:

§ 29.6.5 Requirements for operations on atomic types

void atomic_store(volatile A * object, C desired) noexcept;
void atomic_store(A * object, C desired) noexcept;
void atomic_store_explicit(volatile A * object, C desired, memory_order order) noexcept;
void atomic_store_explicit(A * object, C desired, memory_order order) noexcept;
void A ::store(C desired, memory_order order = memory_order_seq_cst) volatile noexcept;
void A ::store(C desired, memory_order order = memory_order_seq_cst) noexcept;
  1. Requires: The order argument shall not be memory_order_consume, memory_order_acquire, nor memory_order_acq_rel.
  2. Effects: Atomically replaces the value pointed to by object or by this with the value of desired. Memory is affected according to the value of order.

And so on, using the word Atomically in every case it applies.

Instead of repeating themselves for add/+, sub/-, and so for |, &, and ^, they have a key/op table that applies to this block:

C atomic_fetch_key (volatile A * object, M operand) noexcept;
C atomic_fetch_key (A * object, M operand) noexcept;
C atomic_fetch_key _explicit(volatile A * object, M operand, memory_order order) noexcept;
C atomic_fetch_key _explicit(A * object, M operand, memory_order order) noexcept;
C A ::fetch_key (M operand, memory_order order = memory_order_seq_cst) volatile noexcept;
C A ::fetch_key (M operand, memory_order order = memory_order_seq_cst) noexcept;
  • 28 Effects: Atomically replaces the value pointed to by object or by this with the result of the computation applied to the value pointed to by object or by this and the given operand. Memory is affected according to the value of order. These operations are atomic read-modify-write operations (1.10).
  • 29 Returns: Atomically, the value pointed to by object or by this immediately before the effects.
  • 30 Remark: For signed integer types, arithmetic is defined to use two’s complement representation. There are no undefined results. For address types, the result may be an undefined address, but the operations otherwise have no undefined behavior.

The only thing that's optional is ordering / synchronization with loads/stores in other threads (for atomicity without synchronization, use memory_order_relaxed).

In fact, there's no way to "turn off" atomicity for loading a wide type where that's expensive (e.g. before a CAS on atomic<pointer_and_ABAcounter> which compiles to lock cmpxchg16b on x86). I used a union hack in that answer to efficiently load the struct.

And more importantly, the union is a workaround for gcc not optimizing ptr_and_counter.ptr to a load of just the pointer, which I think is safe at least on x86. Instead gcc insists on atomically loading the whole struct and then getting the pointer from the result. That's very bad when it's a 16-byte struct on x86-64, and fairly bad on x86-32. (See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80835)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks for the standard quote. It's a bit interesting that here they introduce a new term, _indivisible_ to describe how values must be atomic, since it I guess it would be weird to say "atomic values must still be atomic". It's also interesting they mention this as in the context of `relaxed` as in "they must still be indivisible" - but do they mention the overall indivisibility elsewhere? – BeeOnRope Sep 06 '17 at 18:19
  • @BeeOnRope: yes. And the wording in the relaxed section implies that other orderings have the property too. What has you wondering if atomics are always atomic? I think it's pretty clear that barring compiler bugs, they are always atomic. – Peter Cordes Sep 06 '17 at 19:44
  • I think I was sleepy or drunk when I asked the question, but then rather than deleting I still wanted to see the text which spells it out. I don't have a copy of the standard but if it has text like "will read a value which was written by some other thread" like cppreference does, then I guess that's it. – BeeOnRope Sep 06 '17 at 20:05
  • @BeeOnRope: You can have a pdf copy of the draft standard if you click the link I added... If you find the language you're looking for, feel free to edit this answer. I agree it would be good to have this question answered with the best possible quote from the standard, but don't have time to go digging for it. – Peter Cordes Sep 06 '17 at 20:22
  • 1
    @BeeOnRope: updated with technical language from the standard. – Peter Cordes Sep 07 '17 at 05:12