jeudi 15 octobre 2020

Why can't you use relaxed atomic operations to synchronize memory, if there is a thread join in between?

I'm watching this Herb Sutter talk: https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2

Looking at two examples (around 1:15 mark), one makes sense to me and the other does not.

First example (makes sense):

Note count is an atomic int.

Here, using relaxed memory ordering for the load/store on count is said to be ok since thread exit happens before returning from a join on a thread. That means the relaxed adds happen before the relaxed load.

Child thread:

while (...) {
  if (...) {
    count.fetch_add(1, memory_order_relaxed);
  }
}

Main thread:

int main() {
  launch_workers();
  join_workers();
  cout << count.load(memory_order_relaxed);
}

Second example (unclear):

Note dirty and stop are atomic booleans.

Here, the talk says that the store and load on dirty must respectively use release and acquire ordering, since dirty is used to publish some data that is read for cleanup.

Child thread:

while(!stop.load(memory_order_relaxed)) {
  if (...) {
    // publish some data
    dirty.store(true, memory_order_release)
  }
}

Main thread:

int main() {
  launch_workers();
  stop = true;
  join_workers();
  if (dirty.load(memory_order_acquire)) {
    // read published data to clean up
  }
}

My question is, in the second example why can't you apply the same 'happens before' relationship between the thread exit and returning from a join on a thread to synchronize the memory?

The way I thought it would work with relaxed memory ordering on the operations of dirty is:

[Child thread does some work, including publishing data]

[Child thread exit] -(happens before)-> [Return from join on child thread in join_workers() call]

[Main thread both sees dirty as true and can safely read the published data to do cleanup]

Aucun commentaire:

Enregistrer un commentaire