简体   繁体   中英

Synchronization with C++ atomic memory fence

I have a question about the synchronization of the code below using memory fence.

std::atomic<int> a = 0;
std::atomic<int> b = 0;

void increase_b() {
  std::atomic_thread_fence(std::memory_order_release);
  b.store(1, std::memory_ordered_relaxed);
}

bool diff() {
  int val_a = a.load(std::memory_ordered_relaxed);
  int val_b = b.load(std::memory_ordered_relaxed);
  return val_b > val_a;
}

void f1() {
  increase_b();
  std::atomic_thread_fence(std::memory_order_seq_cst);
}

void f2() {
  std::atomic_thread_fence(std::memory_order_seq_cst);
  bool result = diff();
}

int main() {
  std::thread t1(f1);
  std::thread t2(f2);
  t1.join(); t2.join();
}

Assume t1 has finished f1 and then t2 just started f2 , will t2 see b incremented?

Your code is overcomplicated. a=0 never changes so it always reads as 0. You might as well just have atomic<int> b=0; and only a single load that just return b.load .

Assume t1 has finished f1 and then t2 just started f2, will t2 see b incremented?

There's no way for you to detect that this is how the timing worked out, unless you put t1.join() ahead of std::thread t2(f2); construction. That would require that everything in thread 2 is sequenced after everything in thread 1. (I think even without a seq_cst fence at the end of f1, but that doesn't hurt. I think thread.join makes sure everything done inside a thread is visible after thread.join )

But yes, that ordering can happen by chance, and then of course it works.

There's no guarantee that's even a meaningful condition in C++ terms.

But sure for most (all?) real implementations it's something that can happen. And a thread_fence(mo_seq_cst) will compile to a full barrier that blocks that thread until the store commits (becomes globally visible to all threads). So execution can't leave f1 until reads from other threads can see the updated value of b . (The C++ standard defines ordering and fences in terms of creating synchronizes-with relationships, not in terms of compiling to full barriers that flush the store buffer. The standard doesn't mention a store buffer or StoreLoad reordering or any of the CPU memory-order things.)

Given the synthetic condition, the threads actually are ordered wrt. each other, and it works just like if everything had been done in a single thread.


The loads in diff() aren't ordered wrt. each other because they're both mo_relaxed . But a is never modified by any thread so the only question is whether b.load() can happen before the thread even started, before the f1 store is visible. In real implementations it can't because of what " and then t2 just started f2" means. If it could load the old value, then you wouldn't be able to say " and then ", so it's almost a tautology.

The thread_fence(seq_cst) before the loads doesn't really help anything. I guess it stops b.load() from reordering with the thread-startup machinery.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM