1

Following up from Multiple assignment in one line, I would be curious to know how that would work for atomic data types, in particular with an example with boolean type.

Given:

class foo {
    std::atomic<bool> a;
    std::atomic<bool> b;
  public:
    void reset();
    [...] //Other methods that might change a and b
}

Is there any difference between:

void foo::reset() {
  a = false;
  b = false;
}

And:

void foo::reset() {
  a = b = false;
}

Namely, in the second case, can it happen that after b is assigned false, another thread set b to true before b is read to assign its value to a, so that at the end of the instruction the value of a is true?

(This would also imply that the latter version is seemingly less efficient)

Antonio
  • 19,451
  • 13
  • 99
  • 197
  • 2
    Hard to say without seeing the actual compiler output, but `std::atomic::operator=()` is supposed to return the value that is passed to it, so I don't think `a = ...` would actually read the value of `b` at all, it would just receive `false` from `operator=`'s output. Now, in a multithreaded situation, `b` could still be `true` when `reset()` exits, but that is another matter. – Remy Lebeau Oct 09 '17 at 22:34
  • @RemyLebeau Well, then that pretty much answer my question, there shouldn't be any difference, otherwise the compiler would be sub-optimal. Thanks! (Sure, at the end of `reset` it can be that `b` is `true` if another thread intervened) – Antonio Oct 09 '17 at 22:39

2 Answers2

3

Yes, there is a difference between

a = false;
b = false;

and

a = b = false;

if a and b are atomic. Since assignment is done from right to left, the latter is equivalent to

b = false;
a = false; // since atomic::operator= (from above) returns its argument

which differs from the very first version because a and b are atomic and the assignment is done as if std::atomic::store was called with the memory order memory_order_seq_cst. Thereby, the memory model guarantees

a single total modification order of all atomic operations that are so tagged.

As a result, a second thread performing atomic loads (bool a_observed = a.load(); bool b_observed = b.load();) in the reverse order of storing (a = b = false;) may observe the changes in one of the following three ways:

  • old values for both b and a
    • load a, load b, store b, store a
  • new value for b and old value for a
    • store b, load a, store a, load b
    • store b, load a, load b, store a
    • load a, store b, store a, load b
    • load a, store b, load b, store a
  • new values for both b and a
    • store b, store a, load a, load b

In contrast, the memory_order_seq_cst for storing b before a while (in the other thread) loading a before b guarantees that the following is never observed:

  • new value for a and old value for b
Julius
  • 1,816
  • 10
  • 14
1

Godbolt link

There's minimal difference between the two. The only real difference is that the assignment order is flipped. If you turn on optimizations, it's indistinguishable.

Wijagels
  • 148
  • 1
  • 2
  • 12
  • Very cool link and website! Actually when using a bit more complex definition, the difference will be in that the order will be preserved even when optimized https://godbolt.org/g/kozyTp – Antonio Oct 10 '17 at 18:24