Гарантирует ли хранилище memory_order_seq_cst, что загрузка memory_order_relaxed будет считывать записанное значение?

Гарантированно ли в следующем коде значение x_value равно 42?

std::atomic<int> x;

Thread A:
  x.store(42, std::memory_order_seq_cst);
  wakeThreadB();

Thread B:
  int x_value = x.load(std::memory_order_relaxed);
  assert(x_value == 42);

Я попытался проверить это, и мне показалось, что поток B всегда считывает правильное значение. Но я не уверен, что это гарантировано.

Зависит от механизма, который вы используете для пробуждения потока B. Если это какое-то атомарное расслабленное чтение/запись, то нет гарантии, что x_value будет 42. При этом не уверен, на какой архитектуре он может выйти из строя.

ALX23z 13.02.2023 14:44

@emptysamurai: проблема с синхронизацией в том, что все детали имеют значение. Не зная, как именно реализовано пробуждение, я не думаю, что на ваш вопрос можно ответить.

Mat 13.02.2023 14:48

На самом деле у меня есть тихий сложный код, но в конечном итоге Thread B разблокируется с помощью condional_variable или существует memory_order_seq_cst, хранимая потоком B в переменной состояния, что гарантирует, что он не перейдет в спящий режим и будет считывать значение из x

emptysamurai 13.02.2023 14:52
std::condition_variable использует мьютекс для синхронизации (вам нужно вызвать блокировку/разблокировку в потоке A, чтобы он работал правильно). Таким образом, два потока будут синхронизировать свои данные. Это означает, что вы можете отказаться от атомарного и использовать вместо него обычное целое число, и оно гарантированно будет равно 42.
ALX23z 13.02.2023 14:56

Детали имеют значение, но сама по себе эта расслабленная нагрузка гарантированно будет только атомарной (en.cppreference.com/w/cpp/atomic/memory_order).

Persixty 13.02.2023 14:58

Более строго упорядоченное хранилище не становится видимым раньше и не заставляет другие потоки ждать, пока они его не увидят. Это только заставляет другие операции в том же потоке ждать, чтобы получить требуемый порядок. Единственное, что может синхронизироваться между A и B, — это wakeThreadB(), но вы не показали, как это реализовано.

Peter Cordes 13.02.2023 20:08
Руководство для начинающих по веб-разработке на React.js
Руководство для начинающих по веб-разработке на React.js
Веб-разработка - это захватывающая и постоянно меняющаяся область, которая постоянно развивается благодаря новым технологиям и тенденциям. Одним из...
Разница между Angular и React
Разница между Angular и React
React и AngularJS - это два самых популярных фреймворка для веб-разработки. Оба фреймворка имеют свои уникальные особенности и преимущества, которые...
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
1
6
80
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Расслабленная загрузка не синхронизируется ни с какой другой загрузкой/сохранением до или после нее.

Также обратите внимание, что семантика порядка памяти связана с видимостью другой работы, выполненной в отношении переменной синхронизации.

Так, например. следующее будет неправильным:

std::atomic<int> x;
int y;

void func_A()
{
  y = 1;
  x.store(42, std::memory_order_seq_cst); // "releases" y = 1
}

void func_B()
{
  while (x.load(std::memory_order_relaxed) != 42) {} // does NOT "acquire" anything
  assert(y == 1); // not guaranteed
}

int main()
{
  std::thread a(&func_A), b(&func_B);
  a.join();
  b.join();
}

Обязательное примечание здесь: «это всегда работает на моей машине» не делает это правильным; без синхронизации это гонка данных, форма неопределенного поведения.

Но в вашем конкретном случае, если под wakeThreadB() вы подразумеваете создание std::thread экземпляра с кодом потока B в качестве функции потока, тогда код на самом деле правильный - std::thread создание является событием синхронизации (см. [thread.thread.constr]/5), поэтому любая загрузка в потоке B гарантированно увидит все, что было сделано до того, как поток B был запущен.

Это означает, что атомарное хранилище x вообще не имеет значения, код будет правильным даже с неатомарным int:

void func_B();

int x;
std::thread t;

void func_A()
{
  x = 42;
  t = std::thread(&func_B); // "releases" x = 42
}

void func_B() // "acquires" x = 42
{
  assert(x == 42); // guaranteed success
}

int main()
{
  func_A();
  t.join();
}

Точно так же std::condition_variable использует мьютекс внутри, а освобождение/блокировка мьютекса является событием синхронизации (см. [thread.mutex.requirements.mutex]/25), поэтому уведомление другого потока через condition_variable также будет работать правильно без необходимости любые атомы.

Другие вопросы по теме