2

I've been reading about C++ memory models and think I have a grasp of the basic concepts of happens-before, synchronizes-with and inter-thread happens-before. However, applying them in some examples has been difficult. In particular I have been thinking about the following sequence of operations when they are repeated:

Thread 1
1. Atomic store of X in A (seq cst).
2. Write X to B.
3. Atomic store of X in C (seq cst).

Thread 2
4. Atomic load of C.
5. Read of B.
6. Atomic load of A.

Now I know that following 4 Thread 2 must see the write made by Thread 1 in 2, but what happens if Thread 1 is spinning and after 3 is setting A then B and then C to Y? I think Thread 2 can see C set to X and B to Y because Thread 1 can have set A and B to Y but not set C to Y yet. This much is clear, but if Thread 2 checks A and finds it is set to X, does that mean Thread 2 must also see B set to X? I've written some code to spin Thread 1 and Thread 2 and found that if Thread 2 sees the same value for A and C then B also has the same value. I know however this does not prove it, which is the reason for this question.

I think the key thing is whether the atomic store in 1 implies anything about the store in 2? If 2 had been done before 1 then it does but that's not the case here. If the result of 2 is seen by Thread 2 before it sees the results of 1 and 3 then Thread 2 would see A and C set to X but B set to Y. Is this possible? How would that be described given the concepts above. Finally, is some other factors coming into play that may mean the program I wrote does work (on x86)?

2

Let's dissect your initial assumption:

I know that following 4 Thread 2 must see the write made by Thread 1 in 2

First example may work:

      Thread 1                    Thread 2 

  1. atomic store in A:  X
  2. write in B : X
  3. atomic store in C : X
                              4. atomic load of C  -> X
                              5. read B  -> X
                              6. atomic load of A -> X

Second example won't:

      Thread 1                    Thread 2 
                        initially A,B and C hold Z

  1. atomic store in A:  X
                              4. atomic load of C  -> Z
  2. write in B : X           5. read B  -> ?? (UB: can b Z, can be X, can be a mix or anything else  
  3. atomic store in C : X
                              6. atomic load of A -> X

Two problems happen in this example, first the load of C can occur before the write, causing the previous value to be fetched. Second, and worse, the access of B is not guaranteed to be atomic and not protected against a race: so you might experience UB.

Now to your question:

what happens if Thread 1 is spinning and after 3 is setting A then B and then C to Y ?

Let's look at a potential scenario:

      Thread 1                    Thread 2 

  1. atomic store in A:  X
  2. write in B : X
  3. atomic store in C : X
                              4. atomic load of C  -> X
  n. atomic store in A: Y     5. read B  -> X
  n+1. write in B: Y          6. atomic load of A -> Y
  n+2. atomic sore in C : Y

Again, you might not have what you've expected. Here I get Y in A but I could have got X. I could get X in B, but I could as well have got Y or even an unexpected value in case of a race.

Conclusion

The happen-before relation within a single thread is straight forward and follows the dermined/predictible flow of control.

As soon as you have a synchronization between two threads (here the reading of atomic), the "inter-thread happen-before" is not predetermined. It depends on the point fo time of the synchronization, and must be analyzed by looking at the relative happen-before in each thread, not forgetting that immediately after other things may still change.

Finally, avoid non-atomic shared variables, unless you protect them with a lock to prevent data races.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.