пара слияния-выпуска из исполнения ордера - PullRequest
6 голосов
/ 14 декабря 2010

Я думаю о том, возможно ли для атомарной переменной загрузить старое значение в паре «захват-выпуск». Предположим, у нас есть атомарная переменная x, и мы храним эту переменную с семантикой выпуска, а затем загружаем ее с семантикой получения. Возможно ли теоретически прочитать старое значение?

std::atomic<int> x = 0;

void thread_1()
{
   x.store(1, std::memory_order_release);
}
void thread_2()
{
   assert(x.load(std::memory_order_acquire) != 0);
}

если поток функции 1 завершен, когда поток 2 загружает x (поэтому новое значение сохраняется), возможно ли для потока 2 загрузить старое значение из x? Другими словами, если фактическое сохранение в x выполняется до загрузки, может ли assert сработать?

Насколько я понял из статей в интернете, это возможно, но я не могу понять, почему. Ограничение памяти, сгенерированное функцией store to x, гарантирует очистку буфера хранилища, в то время как ограничение памяти при загрузке из x гарантирует недействительность строки кэша, поэтому оно должно считывать текущее значение.

добавил

Означает ли это, что приобретение-выпуск само по себе не имеет принудительного упорядочения? Это только то, что было сделано до того, как релиз произойдет до релиза, и все, что будет сделано после получения, произойдет после него, поэтому пара переход-релиз навязывает порядок выполнения других операций (почему ??). Я правильно понял? Означает ли это, что в приведенном ниже коде assert гарантированно не срабатывает

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

void thread_1()
{
   y.store(1, std::memory_order_relaxed);
   x.store(1, std::memory_order_release);
}
void thread_2()
{
   x.load(std::memory_order_acquire);
   assert(y.load(std::memory_order_relaxed) != 0);
}

конечно, снова, если поток 1 уже закончил хранилище. Если мы заменим x.load на while (x.load () == 0), это будет работать на 100%, но я не знаю, что заставляет это работать.

А что, если я заменю код с кодом ниже

std::atomic<int> x = 0;

void thread_1()
{
   x.exchange(1, std::memory_order_acq_rel);
}
void thread_2()
{
   assert(x.exchange(0, std::memory_order_acq_rel) != 0);
}

Это что-то меняет?

Спасибо.

1 Ответ

5 голосов
/ 14 декабря 2010

Вы можете рассмотреть функции сохранения / загрузки с порядком освобождения / получения памяти следующим псевдокодом:

template<class T>
struct weak_atomic
{
   void store(T newValue)
   {
      ReleaseBarrier();
      m_value = newValue;
   }

   T load()
   {
      T value = m_value;
      AcquireBarrier();
      return value;      
   }

   volatile T m_value;
}

Вы сказали

Ограничение памяти, сгенерированное хранилищем для xГарантии очистки буфера хранилища

Как я понимаю, освобождение барьера памяти приведет к тому, что ЦП очистит свой буфер хранилища, но это будет сделано до того, как применит новое значение для x.Таким образом, кажется возможным прочитать старое значение из x другим процессором.

В любом случае, слабая атомика - очень сложная область.Убедитесь, что вы понимаете барьеры памяти, прежде чем приступить к программированию без блокировки.

ДОБАВЛЕНО

Кажется, вы все еще путаете с барьерами памяти.Это довольно распространенный пример их использования.

volatile int  x;
volatile bool ok;

void thread_1()
{
   x = 100;
   ok = true;
}

void thread_2()
{
   if (ok)
   {
      assert(x == 100);
   }
}

Из-за неправильного выполнения вы можете получить следующую последовательность:

thread 1 sets ok to true
thread 2 checks ok is true and reads some garbage from x
thread 1 sets x to 100 but it is too late

Другая возможная последовательность:

thread 2 reads some garbage from x
thread 2 checks for ok value

Мы можем исправить это с помощью освобождения и получения барьеров памяти.

volatile int  x;
volatile bool ok;

void thread_1()
{
   x = 100;
   ReleaseBarrier();
   ok = true;
}

void thread_2()
{
   if (ok)
   {
      AcquireBarrier();
      assert(x == 100);
   }
}

ReleaseBarrier() гарантирует, что записи в память не могут перепрыгнуть через барьер.Это означает, что ok устанавливается на true, только когда x уже содержит допустимое значение.

AcquireBarrier() гарантирует, что чтение памяти не может перепрыгнуть через барьер.Это означает, что значение x читается только после проверки состояния ok.

Именно так предполагается использовать пару освобождения / получения.Мы можем переписать этот пример с моим weak_atomic.

volatile int  x;
weak_atomic<bool> ok;

void thread_1()
{
   x = 100;
   ok.store(true);
}

void thread_2()
{
   if (ok.load())
   {
      assert(x == 100);
   }
}
...