Correctness of 'Concurrency in Action' atomic operation example

Viewed 97

I've been studing 'Concurency in Action' position for some time and I have a problem with understanding following example of code (Listing 5.2):

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

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

void reader_thread()
{
 while(!data_ready.load())
 {
  std::this_thread::sleep(std::milliseconds(1));
 }
 std::cout<<”The answer=”<<data[0]<<”\n”;
}
void writer_thread()
{
 data.push_back(42); //write of data
 data_ready=true; //write to data_ready flag
}

The book explaines:

(...) The write of the data happens-before the write to the data_ready flag (...)

My concern is that the sentence does not cover the out-of-order execution. From my understanding out of order execution may happen when at least two instruction do not have depended operands. Taking this into account:

data_ready=true

does not need anything from

data.push_back(42)

to be executed. As a result of that it is not guaranteed that:

The write of the data happens-before the write to the data_ready flag

Is my understadning correct or there is something in out-of-order execution that I don't understand causing misunderstaning of given example?

EDIT

Thank you for answers, it was helpful. My misunserstanding was a result of not knowing that atomic types not only prevents from partialy channing a variable, but also acts as memory barrier.

For example following code may be reordered in many combinations by either compiler or processor:

d=0;
b=5;
a=10
c=1;

Resulting with following order (one of many possibilities):

b=5;
a=10
c=1;
d=0;

It it is not a problem with single-thread code since none of expressions have depended operands on other, but on multithreaded application may result of undefined behaviour. For example following code (initial values: x=0 and y=0):

Thread 1:       Thread 2:   
x=10;          while(y!=15);
y=15;          assert(x==10);

Without reordering of code by compiler or reordering execution by processor we could say: "Since assigement y=15 allways happens after assigement x=10 and assert happens after while loop the assert will never fail" But it's not true. The real execution order may be as below (one of many possible combinations):

Thread 1:       Thread 2:   
x=10; (4)         while(y!=15); (3)
y=15; (1)         assert(x==10); (2)

By default an atomic variable ensures sequentionally consistent ordering. If y in example above was atomic with memory_order_seq_cst default parameter following sentences are true: - what happens before in thread 1 (x=10) it is also visible in thread 2 as happening before. - what happens after while(y!=15) in thread 2 it is also visible in thread 1 as happening after As a result of it assert will never fail.

Some of sources that may help with understaning:

2 Answers

I understand your concerns, but the code from book is fine. Every operation with atomics is by default memory_order_seq_cst, which means that everything that happened before the write in one of threads happens before read in the rest. You can imagine atomic operations with this std::memory_order like this:

std::atomic<bool> a;
//equivalent of a = true
a.assign_and_make_changes_from_thread_visible(true);

//equvalent of a.load()
a.get_value_and_changes_from_threads();

From Effective Modern C++, Item 40, it says "std::atomics imposes restrictions on how code can be reordered, and one such restriction is that no code that, in the source cod, precedes a write of std::atomic variable may take place afterwards." The note is this is true for when using sequential consistency which is a fair assumption.

Related