C++ Multi-threading: Visible side-effects of non-atomic variables
Asked Answered
S

2

2

There is a part in the C++ standard about multi-threading memory model that I don't understand.

A visible side effect A on a scalar object or bit-field M with respect to a value computation B of M satisfies the conditions:

  • A happens before B and

  • there is no other side effect X to M such that A happens before X and X happens before B.

The value of a non-atomic scalar object or bit-field M, as determined by evaluation B, shall be the value stored by the visible side effect A.

And also according to the C++ standard, a "happens before" relationship between threads must be established by "synchronizes with" or "is dependency-ordered before", so a "happens before" relationship will not be established without inter-thread synchronization.

Now suppose there are two threads T1 and T2, both started by the main thread and never do any synchronization with each other (so there would not be any "happens before" relationships established between T1 and T2). If T1 writes to a non-atomic variable M, then according to the quote above, T2 should never see M modified by T1, because there is no "happens before" relationship between T1 and T2.

Instead, T2 has "synchronizes with" relationship established with the main thread at the time T2 starts, so T2 should see the value of M set by the main thread before it was started by the main thread, because there is a "happens before" relationship between the main thread and T2.

Right? However I did an experiment on my machine and it was not the case. What is wrong?

Swinge answered 18/4, 2020 at 0:23 Comment(1)
I won't post an answer because I fear it would be censored, but the std does not in any way shape or form define the behavior of MT programs. The intuition you need is: use mutual exclusion for variable access.Antibaryon
T
2

T1 writes to a non-atomic variable M, then according to the quote above, T2 should never see M modified by T1

Consider the following:

Two actions are potentially concurrent if

  • they are performed by different threads, or

  • they are unsequenced, at least one is performed by a signal handler, and they are not both performed by the same signal handler invocation.

T2's read of M and T1's write to M are "potentially concurrent". Next:

Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location.

T2's read of M conflicts with T1's write to M. So these "potentially concurrent" actions conflict.

Lastly, we come to:

The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.

T2's read of M does not happen before T1's write to M, nor does T1's write to M happen before T2's read of M. Therefore, you have a data race.

And data races are undefined behavior. It's not that T2 won't see the write to M; it's that it could see anything: the old value, the new value, some third value, nasal daemons issuing forth, anything.

Ternary answered 18/4, 2020 at 0:37 Comment(1)
Thank you for explanation! It is very clear. So in this situation I have to ensure either T1's write "happens before" T2's read, or T2's read "happens before" T1's write. Otherwise I have a "data race", which is undefined behavior.Swinge
N
0

In addition to the perspective presented by Nicol Bolas in his answer, I would point out the following error of conclusion in the OP's question.

T2 should never see M modified by T1, because there is no "happens before" relationship between T1 and T2

The quoted section in the question intuitively means that if

  1. A happens before B (let's call this AHB), and
  2. No modification X happens between A and B (let's call this NXBAB)

then

Side effect of A is visible to B (let's call this AVTB)

Succinctly,

If (AHB and NXBAB) is true then it implies AVTB is also true

According to boolean algebra, and even basic common sense, this does not mean that

If (AHB and NXBAB) is not true then it implies AVTB is also not true

In general, if A does not happen before B then the side effects of A are not guaranteed to be visible to B. They may still be visible, in sane and insane ways, and that's what the Undefined Behaviour in Nicol Bolas's answer is about.

Nag answered 6/10 at 11:56 Comment(2)
I understand your point about ¬P⇏¬Q. However, see my next paragraph. If we consider the relationship between the main thread and T2, we can conclude T2 must see what the main thread writes (and therefore not what T1 writes). This issue can only be solved by the "undefined behavior" text in the standard.Swinge
@Swinge - Your conclusion in the above comment is right. The code described in the question does have undefined behavior. The tricky part to understand is that this undefined behavior also includes "T2 may see M modified by T1", and hence experiments may be misleading. I suppose, to double emphasize this point the proverbial nasal demons are also included in UB. Anything is possible. To eliminate ambiguity of visibility we need synchronization. In this case presence of ambiguity is presence of UB.Nag

© 2022 - 2024 — McMap. All rights reserved.