Попытка понять пример кода с атомарностью из книги «Параллельность C ++ в действии» (стр. 133) - PullRequest
0 голосов
/ 01 февраля 2020

В настоящее время я пытаюсь понять следующий пример кода из книги «Параллельность C ++ в действии»:

#include <stdio.h>

#include <atomic>
#include <thread>

#undef NDEBUG // for release builds
#include <assert.h>

std::atomic<bool> x, y;
std::atomic<int> z;

void write_x()
{
    x.store(true, std::memory_order_release);
}

void write_y()
{
    y.store(true, std::memory_order_release);
}

void read_x_then_y()
{
    while (!x.load(std::memory_order_acquire))
        ;
    if (y.load(std::memory_order_acquire))
        ++z;
}

void read_y_then_x()
{
    while (!y.load(std::memory_order_acquire))
        ;
    if (x.load(std::memory_order_acquire))
        ++z;
}

int main(int argc, char *argv[])
{
    for (;;)
    {
        x = false;
        y = false;
        z = 0;

        std::thread a(write_x);
        std::thread b(write_y);
        std::thread c(read_x_then_y);
        std::thread d(read_y_then_x);

        a.join();
        b.join();
        c.join();
        d.join();

        assert(z.load() != 0);
    }

    return 0;
}

Я не совсем понимаю, как может срабатывать утверждение, как утверждается в книге , Как строки кода четырех потоков должны быть расположены рядом, чтобы z было равно нулю. В принципе, это может произойти только в том случае, если после спин-блокировки строки можно переставить, верно? Но, как я понял, Acquire гарантирует, что Грузы и Склады после Acquire не могут быть переупорядочены до Acquire. И нагрузка до приобретения не может быть переупорядочена после приобретения.

Заранее спасибо за разъяснения.

РЕДАКТИРОВАТЬ

Я думаю, что все это будет быть более понятным, если кто-то объяснит это с технической точки зрения (кеш, заборы, переупорядочение, грипп sh, недействительность и т. д. c.) и использование тематического исследования с z = 0.

Пример для объяснения это, но мне это нужно для z = 0 (технически):

Thread 1  Thread 2    Thread 3        Thread 4

                      while (!x);     while (!y);
x=1                   while (!x);     while (!y);
                      if (y) // y=0   while (!y);
          y=1                         while (!y);
                                      while (!y);
                                      if (x) // x=1
                                      z++

Результат: z = 1

То, что я также не совсем понимаю, заключается в следующем: почему ограничение выпуска должны быть указаны для магазинов вообще? Почему здесь недостаточно расслабленного? В принципе, нет никаких дополнительных операций (загрузок, хранилищ), указанных выше хранилищ, которые должны быть промыты или чье изменение порядка должно быть предотвращено через забор. Разве write_x не эквивалентно этому?

// Store / loads which may not cross fence boundary.
// No store / loads here, so why needs to be a fence here?
std::atomic_thread_fence(std::memory_order_release);
x.store(true, std::memory_order_relaxed);

Я долго запускал приведенный выше пример на своем x86-64 (бесконечно для -l oop) и еще не сталкивался с утверждением , Это связано с сильной моделью памяти Intel?

Ответы [ 2 ]

1 голос
/ 01 февраля 2020

Проблема в том, что два хранилища x и y не упорядочены друг с другом, поэтому их нельзя использовать для синхронизации видимости:

atomic x,y,z;
void write_x() {
  x.store(true, release);
}
void write_y() {
  y.store(true, release);
}

void read_x_then_y() {
  while (!x.load(acquire))
    ;
  // has seen x==true, but there is no guarantee that y.load() will return true
  if (y.load(acquire))
    ++z;
}

void read_y_then_x() {
  while (!y.load(acquire))
    ;
  // has seen y==true, but there is no guarantee that x.load() will return true
  if (x.load(acquire))
    ++z;
}

void main() {
  x=false;
  y=false;
  z=0;
  thread a(write_x);
  thread b(write_y);
  thread c(read_x_then_y);
  thread d(read_y_then_x);
  join(a,b,c,d);
  assert(z.load()!=0);
}

Так что может случиться, что оба потока прочитают false после в то время как l oop, и, следовательно, z никогда не увеличивается.

Дело в том, что операции хранилища используют memory_order_release, поэтому единого итогового порядка не существует, и именно поэтому расположение строк расположено рядом сторона не помогает объяснить это поведение.

0 голосов
/ 01 февраля 2020

Приобретение / освобождение обеспечивает только упорядочение с хранилищами перед операцией получения в потоке, выполняющем операцию получения.

Нет сохранения до операций получения, и, следовательно, фактически никакого дополнительного упорядочения не накладывается относительно ослабленного case.

Единственный порядок, наложенный также в смягченном случае, состоит в том, что переменные atomi c индивидуально имеют полный порядок модификации, с которым согласуются все потоки. Но два потока могут не согласиться с тем, какой из двух хранилищ с различными атомами в вашем примере произошел первым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...