Без блокировки один производитель / один потребитель кольцевой буфер - PullRequest
0 голосов
/ 19 января 2019

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

В этой реализации есть только уникальный поток, который может вызвать функцию push(), и другой уникальный поток, который может вызвать функцию pop().

Вот код Producer:

bool push(const Element& item)
{       
  const auto current_tail = _tail.load(std::memory_order_relaxed);  //(1)
  const auto next_tail = increment(current_tail);

  if(next_tail != _head.load(std::memory_order_acquire))            //(2)               
  {     
    _array[current_tail] = item;                                    //(3)
    _tail.store(next_tail, std::memory_order_release);              //(4)
    return true;
  }
  return false; // full queue
}

Вот код Consumer:

bool pop(Element& item)
{
  const auto current_head = _head.load(std::memory_order_relaxed);    //(1)
  if(current_head == _tail.load(std::memory_order_acquire))           //(2)
    return false; // empty queue

  item = _array[current_head];                                       //(3)
  _head.store(increment(current_head), std::memory_order_release);   //(4)
  return true;
}

Я понимаю, почему утверждения Producer (4) и Consumer (2) абсолютно необходимы, потому что мы должныубедитесь, что все пишут, что произошло до , (4) released store от Producer будут видимыми побочными эффектами, как только consumer увидит сохраненное значение.

Я также понимаю, почемуТребуется оператор Consumer (4), чтобы убедиться, что загрузка Consumer (3) будет выполнена до того, как будет выполнено сохранение Consumer (4).

Вопрос

  • Почему нагрузку Producer (2) необходимо выполнять с приобретать семантику (вместо relaxed )?Это предотвращает повторную запись Producer (3) or (4) перед условием (во время компиляции или во время выполнения)?

Ответы [ 2 ]

0 голосов
/ 19 января 2019

нам нужно доказать, что

_array[current_tail] = item; // push(3)

исключено после того, как соответствует (current_head == current_tail)

item = _array[current_head]; // pop(3)

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

_head.load(std::memory_order_acquire) // push(2)

, синхронизированный с

_head.store(increment(current_head), std::memory_order_release);   //pop(4)

через упорядочение Release-Acquire:

все записи в память ( pop (3) ), которые произошли до того, как выпуск атомарного хранилища ( pop (4) ) в _head становятся видимыми побочными эффектами, как толькополучение атомной нагрузки ( push (2) ) завершено на _head.

, поэтому код производителя после push (2) завершен, гарантированно виден результат pop (3) .это означает, что данные из _array[current_head] копируются в элемент, и результат этой операции отображается для кода источника после push (2) , поэтому _array[current_head] уже свободен.

с другой стороны от memory_order_acquire описание загрузки - до этой загрузки нельзя изменить порядок чтения или записи ( push (3) ) в текущем потоке.так что push (3) будет выполнено уже после push (2) загрузка завершена, но на этом этапе pop (3) уже завершена

item = _array[current_head];                                        //pop(3)
_head.store(increment(current_head), std::memory_order_release);    //pop(4)
-----
    _head.load(std::memory_order_acquire);                          //push(2)
    _array[current_tail] = item;                                    //push(3)         
0 голосов
/ 19 января 2019

Барьеры памяти не позволяют ЦП переупорядочивать доступ к объекту Element, который не использует блокировки, через доступ к структуре очереди (здесь реализовано использование индексов, но указатели одинаково жизнеспособны).

При использовании вашей нумерации важно, чтобы (3) выполнялось между (2) и (4), и барьер памяти обеспечивает это.

Точный случай, о котором вы спрашиваете о (2) -vs- (3) в источнике, предотвращает умозрительную перезапись действительных данных, когда очередь заполнена (предлагаемый сайт перекрывается с действительными данными). Без барьера, даже если условие не выполнено, исходные данные будут восстановлены с точки зрения потока источника, промежуточные значения могут быть кратко видны потребителю.

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