Содержит ли такое использование семантики выпуска/приобретения потенциальную гонку данных?

Рассмотрим следующий код, который я нашел на http://preshing.com/20120913/acquire-and-release-semantics/ (но я уверен, что часто встречал его в других местах)

Общий код:

int A = 0;
std::atomic<int> Ready(0);

Код, выполняемый потоком 1:

A = 42
Ready.store(1, std::memory_order_release);

Код, выполняемый потоком 2:

int r1 = Ready.load(std::memory_order_acquire);
int r2 = A;

Тогда можно сказать, что если r1 == 1, то r2 всегда будет 42 и гонки данных не будет.

Мой первый вопрос: содержит ли код тем не менее гонку данных? Я имею в виду в случае, если r1 == 0. Например, поток 1 может быть вытеснен на полпути через хранилище к A, а затем поток 2 может продолжить выполнение.

Я бы подумал, что код для Thread 2 следует переписать так:

int r1 = Ready.load(std::memory_order_acquire);
int r2 = 0;
if( r1 == 1 ) {
    r2 = A;
}

чтобы избежать потенциальной гонки данных.

Это правда?


person Toby Brull    schedule 01.03.2014    source источник


Ответы (1)


Да, ваша интерпретация верна: программа содержит гонку данных из-за безусловного чтения A. Программа упрощена и представляет собой минимальный пример, демонстрирующий работу операции получения-освобождения для блога: автор обсуждает только то, как порядок памяти обеспечивает, чтобы поток «читатель» должен читать 42 из A, если он читает 1 из Ready. Он не говорит об альтернативе, потому что она неуместна.

В реальной программе поток «читатель», вероятно, будет ждать в цикле Ready, чтобы получить желаемую семантику:

int A = 0;
std::atomic<int> Ready(0);

void write_thread() {
  A = 42;
  Ready.store(1, std::memory_order_release);
}

void read_thread() {
  while (!Ready.load(std::memory_order_acquire))
    ;
  int r2 = A;
  assert(r2 == 42);
}

Или используйте условное выражение, как в вашем примере:

int A = 0;
std::atomic<int> Ready(0);

void write_thread() {
  A = 42;
  Ready.store(1, std::memory_order_release);
}

void read_thread() {
  while (true) {
    if (Ready.load(std::memory_order_acquire)) {
      int r2 = A;
      assert(r2 == 42);
      break;
    }

    std::cout << "Not ready yet - try again later.\n" << std:flush;
    std::this_thread::sleep_for(std::chrono::milliseconds{125});
  }
}
person Casey    schedule 01.03.2014