#incl" /> #incl" /> #incl"/>

I currently read about "C++ Concurrency in action", in the memory model part (Chapter 5). In chapter 5.3.1, the author writes:

#include <vector>
#include <atomic>
#include <iostream>

std::vector<int> data;
std::atomic<bool> data_ready(false);

void reader_thread()
{
    while(!data_ready.load())    // (1)
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    std::cout<<”The answer=”<<data[0]<<”\n”;    // (2) 
}

void writer_thread()
{
    data.push_back(42);  // (3)
    data_ready=true;     // (4) 
}

Leaving aside the inefficiency of the loop waiting for the data to be ready (1), you really need this to work, because otherwise sharing data between threads becomes impractical: every item of data is forced to be atomic. You’ve already learned that it’s undefined behavior to have nonatomic reads (2) and writes (3) accessing the same data without an enforced ordering, so for this to work there must be an enforced ordering somewhere.

The required enforced ordering comes from the operations on the std:: atomic variable data_ready; they provide the necessary ordering by virtue of the memory model relations happens-before and synchronizes-with. The write of the data (3) happens-before the write to the data_ready flag (4), and the read of the flag (1) happens-before the read of the data (2). When the value read from data_ready (1) is true, the write synchronizes-with that read, creating a happens-before relationship. Because happens-before is transitive, the write to the data (3) happens-before the write to the flag (4), which happens-before the read of the true value from the flag (1), which happens-before the read of the data (2), and you have an enforced ordering: the write of the data happens-before the read of the data and everything is OK. Figure 5.2 shows the important happens-before relationships in the two threads. I’ve added a couple of iterations of the while loop from the reader thread.

All this might seem fairly intuitive: of course the operation that writes a value happens before an operation that reads that value! With the default atomic operations, that’s indeed true (which is why this is the default), but it does need spelling out: the atomic operations also have other options for the ordering requirements, which I’ll come to shortly

I quite don't understand, why "All this might seem fairly intuitive: of course the operation that writes a value happens before an operation that reads that value!", please help me to understand this statement.

1

There are 1 best solutions below

3
VLL On

The operation that writes value (3) happens after the operation that reads the value (2) because of the data_ready flag. The writer thread enables the flag (4) after the write operation, while the reader thread has a loop that does not continue before the flag is enabled.

The compiler treats an atomic load or store as a memory fence. With the default options of an atomic variable, the compiler does not reorder operations across this fence. So the order is always 3 -> 4 -> (loop ends) -> 2.

With other options the compiler might reorder the operations in each function so that 4 is executed before 3, or 2 is executed before 1.

You can give the options as parameter to load() or store(). See the documentation.