Я некоторое время изучал позицию «Конкуренция в действии», и у меня возникла проблема с пониманием следующего примера кода (Листинг 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
}
В книге объясняется:
(...) Запись данных происходит до записи во флаг data_ready (...)
Меня беспокоит то, что предложение не охватываетвыполнение заказа.Насколько я понимаю, выполнение не по порядку может произойти, когда по крайней мере две инструкции не имеют зависимых операндов.Принимая это во внимание:
data_ready=true
не требуется ничего из
data.push_back(42)
для выполнения.В результате этого не гарантируется, что:
Запись данных происходит - до записи в флаг data_ready
Правильно ли мое понимание иличто-то не в порядке исполнения, что я не понимаю, вызывает неправильное понимание данного примера?
РЕДАКТИРОВАТЬ
Спасибо за ответы, это было полезно.Мое недопонимание было результатом того, что я не знал, что атомарные типы не только предотвращают частичное преобразование переменной, но и действуют как барьер памяти.
Например, следующий код может быть переупорядочен во многих комбинациях компилятором или процессором:
d=0;
b=5;
a=10
c=1;
Получается в следующем порядке (одна из многих возможностей):
b=5;
a=10
c=1;
d=0;
Это не проблема с однопоточным кодом, поскольку ни одно из выражений не зависит от других операндов, нов многопоточном приложении может возникнуть неопределенное поведение.Например, следующий код (начальные значения: x = 0 и y = 0):
Thread 1: Thread 2:
x=10; while(y!=15);
y=15; assert(x==10);
Без переупорядочения кода компилятором или переупорядочения выполнения процессором мы можем сказать: «Поскольку присвоение y = 15 всегда происходит послеassigement x = 10 и assert происходит после цикла while, assert никогда не потерпит неудачу "Но это не так.Реальный порядок выполнения может быть таким, как показано ниже (одна из многих возможных комбинаций):
Thread 1: Thread 2:
x=10; (4) while(y!=15); (3)
y=15; (1) assert(x==10); (2)
По умолчанию атомарная переменная обеспечивает последовательное последовательное упорядочение.Если y в приведенном выше примере является атомарным с параметром по умолчанию для memory_order_seq_cst, то следующие предложения верны: - что происходит раньше в потоке 1 (x=10
), это также видно в потоке 2, как и раньше.- то, что происходит после while(y!=15)
в потоке 2, также видно в потоке 1 как происходящее после. В результате этого assert никогда не потерпит неудачу.
Некоторые источники могут помочь с пониманием: