Корректность примера атомарной операции «Параллельность в действии» - PullRequest
0 голосов
/ 29 декабря 2018

Я некоторое время изучал позицию «Конкуренция в действии», и у меня возникла проблема с пониманием следующего примера кода (Листинг 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 никогда не потерпит неудачу.

Некоторые источники могут помочь с пониманием:

Ответы [ 2 ]

0 голосов
/ 29 декабря 2018

В Effective Modern C ++, пункт 40, говорится, что «std :: atomics налагает ограничения на то, как код может быть переупорядочен, и одним из таких ограничений является то, что ни один код, который в исходном коде не предшествует записи в std :: atomicпеременная может иметь место впоследствии. "Следует отметить, что это верно для последовательной согласованности, которая является справедливым допущением.

0 голосов
/ 29 декабря 2018

Я понимаю ваши проблемы, но код из книги в порядке.Каждая операция с атомами по умолчанию memory_order_seq_cst, что означает, что все, что произошло до записи в один из потоков, происходит до чтения в остальных.Вы можете представить атомарные операции с этим std::memory_order следующим образом:

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...