2

Given two threads A and B I have the following logic:

std::atomic<int> x{0};
std::atomic<int> y{0};

Thread A:
    int y_val = y.load(std::memory_order_acquire);
    if (y_val != 1) {
       int old_x = x.exchange(1, std::memory_order_seq_cst);
       y_val = y.load(std::memory_order_acquire);
    }
    

Thread B:
    y.store(1, std::memory_order_release);
    std::atomic_thread_fence(std::memory_order_seq_cst);
    int x_val = x.load(std::memory_order_relaxed);

Is it possible that after both threads execute code above y_val==0 and x_val==0?

I understand that second load from y can be potentially reordered with store in exchange. But I am not sure if fence can occur between load and store in exchange.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
emptysamurai
  • 161
  • 1
  • 6
  • You're correct about the store side of `x.exchange(seq_cst)` being able to reorder with `y.load(acquire)` because they're not *both* `seq_cst`. ([For purposes of ordering, is atomic read-modify-write one operation or two?](https://stackoverflow.com/q/65568185)). But you don't have an `atomic_thread_fence` in the thread running exchange; not sure what you mean in your last sentence. – Peter Cordes Feb 17 '23 at 10:55
  • I mean can the following sequence of events happen: load in x.exchange(), reordered y.load(), y.store(1), fence, store in x.exchange()? In this case we have y_val == 0 and x_val == 0 – emptysamurai Feb 17 '23 at 11:00
  • 1
    In terms of a model like real CPUs, with coherent shared cache and local (within each core) reordering of access to it (instead of the C++ formalism of creating happens-before relationships), barrier are local. `atomic_thread_fence` in one thread has zero effect on the order things happen in another thread, only the order of this thread's accesses to coherent shared memory. See https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ . So yes, I don't see `x.load(relaxed)` anywhere in your list, and `y.store` is before the barrier, so that subsequence can happen. – Peter Cordes Feb 17 '23 at 11:10
  • Basically you don't have any synchronization, except for `y_val == 1` case, so according to C++ memory model you don't have any guarantees. If you move fence **after** `x.load()` and add `if (x_val == 1)` in "Thread B", then if this condition is `true`, you have guarantee that everything up to `x.exchange()` *happened before* fence. – sklott Feb 17 '23 at 22:50
  • @sklott: If there's no `fence(seq_cst)` between `y.store` and `x.load`, StoreLoad reordering is possible because they're not both seq_cst. The fence is where it needs to be (unless you're planning to add further loads to thread B, like to keep spinning until you see a `1`, which would seriously change things). The thing that needs strengthening is Thread A's `x.exchange` followed by `y.load`, where they're not both seq_cst so they're not both part of the single total order that must exist for seq_cst operations. – Peter Cordes Feb 17 '23 at 23:02
  • I) 1) You have a `memory_order_release` store on `y` in thread B, as first action, so there is by definition nothing "released" because nothing was done. 2) In thread A, you have two statements doing acquire on `y`. But nothing was ever released on `y` so the it brings nothing in. So with that in mind, why not leave all actions on `y` as relaxed? – curiousguy Feb 27 '23 at 04:06
  • II) 1) I don't accept the common assertion that C/C++ has a thread semantics. Or that C and C++ have regular program semantics. They all have a very poor attempt (by any serious standard) at defining a semantic. But some cases are clear. 2) Any Q involving `atomic_thread_fence(std::memory_order_seq_cst)` is inherently complicated and intricate. 3) All complicated Q should mention a specific C++ std version. (Or, we can do a discussion of all std differences.) – curiousguy Feb 27 '23 at 04:13

0 Answers0