Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding C++ memory model : Different values on different runs

What is wrong with the below code? I expect to see 10 to be produced by consumer1 and consumer2, but I see -1 sometimes.

#include <thread>   
#include <atomic>
#include <cassert>
#include <string>

std::atomic<int> global;
void producer()
{
   global.store(10, std::memory_order_release);
}

void consumer1()
{
   int a = global.load(std::memory_order_acquire);
   printf("a in consumer1 %d\n", a);
}

void consumer2()
{
   int a = global.load(std::memory_order_acquire);
   printf("a in consumer2 %d\n", a);
}

int main()
{
    global.store(-1, std::memory_order_seq_cst);
    std::thread t1(producer);
    std::thread t2(consumer1);
    std::thread t3(consumer2);
    t1.join(); t2.join(); t3.join();
}

I see a in consumer1 10 a in consumer2 10 and a in consumer1 -1 a in consumer2 10

If I understand correctly, the thread which does memory_order_acquire always syncs with the thread which does memory_order_release. Am I wrong? I am running on x86-64 bit machine. I am compiling with g++ file.cpp -pthread -std=c++11

like image 722
username_4567 Avatar asked Mar 15 '23 13:03

username_4567


2 Answers

Atomic variables have the very nice property that the value read is a value that has been written before. And with release/acquire semantics it's even the last value written.

In this case, you have 2 writes and two reads. Only the write of -1 is sequenced-before the reads, the write of 10 is not sequenced. Therefore either value may be the last written. It's guaranteed that you read -1 or 10 and not garbage.

like image 80
MSalters Avatar answered Apr 28 '23 17:04

MSalters


If you add a sleep before

global.store(10, std::memory_order_release);

then you can observe consistently a -1.

The key point is that std::memory_order is not a sempaphore-like syncronization, but a rather more subtle matter. See cppreference

like image 45
marom Avatar answered Apr 28 '23 16:04

marom