2016-03-22 2 views
8

В процессе попытки понять, как бороться с кодом, свободным от блокировки, я попытался написать одиночную свободную очередь для одного пользователя/одиночного производителя. Как всегда, я проверял бумаги, статьи и код, особенно учитывая, что это несколько деликатный вопрос.Использование std :: memory_order_consume в свободной блокировке Folly SPSC queue

Итак, я наткнулся на реализацию этой структуры данных в библиотеке Фоли, который можно найти здесь: https://github.com/facebook/folly/blob/master/folly/ProducerConsumerQueue.h

Как каждый безблокировочного очередь я видел, это один, кажется, использует циклический буфер, поэтому мы получили две переменные std::atomic<unsigned int>: readIndex_ и writeIndex_. readIndex_ указывают следующий индекс, по которому мы будем читать, и writeIndex_ следующий, на котором мы будем писать. Кажется достаточно простым.

Таким образом, реализация кажется чистой и довольно простой с первого взгляда, но я нашел одно неприятное. Действительно, некоторые функции, такие как isEmpty(), isFull() или guessSize(), используют std::memory_order_consume для получения значения индексов.

И, честно говоря, я действительно не знаю, какую цель они служат. Не поймите меня неправильно, я знаю об использовании std::memory_order_consume в классическом случае переноса зависимостей через атомный указатель, но здесь мы, похоже, не несем никакой зависимости! Мы получили индексы, целые числа без знака, мы не создаем зависимости. Для меня в этом случае std::memory_order_relaxed эквивалентен.

Однако я не верю себе, что лучше понять порядок памяти, чем те, кто разработал этот код, поэтому я задаю этот вопрос здесь. Есть ли что-то, что я пропустил или неправильно понял?

Я благодарю вас за ваши ответы!

+1

Очень мало случаев, когда 'std :: memory_order_relaxed' действительно будет иметь значение.Вам действительно нужен чип, способный разорвать чтение/запись. Примером является x86, когда вы имеете дело с неуравновешенными данными, но вы не должны. Причина, по которой я говорю все это, заключается в том, что если вы думаете, что что-то нуждается в 'memory_order_relaxed', вы, вероятно, не понимаете использования. Вы пытаетесь предотвратить рвение чтения/записи? – SergeyA

+1

@SideEffects: Я согласен с вами. Последующие обращения к памяти не зависят от значений индекса в этих функциях, поэтому я не вижу, как «std :: memory_order_consume» может внести что-либо полезное. Адреса авторов (авторов) находятся в верхней части файла; возможно, попробуйте отправить их по электронной почте? (Если вы это сделаете, добавьте здесь обновление или ответ. Мне любопытно, что мы чего-то не замечаем.) – Nemo

+0

@SergeyA Да, возможно, я был здесь немного неясен. Все, что я хотел сказать, было то, что я считал, что использование памяти для заказа памяти было, по крайней мере для меня, не добавлением ничего полезного. Но, может быть, я хочу прояснить этот момент, спасибо! – SideEffects

ответ

3

Я думал то же самое, несколько месяцев назад, так что я представил this pull request в октябре, предполагая, что они изменить std::memory_order_consume нагрузки в std::memory_order_relaxed, так как потребляют просто не имеет смысла, так как не было никаких зависимостей, которые могут быть осуществлены с одного с помощью этих функций. Это в конечном итоге генерировать некоторое обсуждение, которое показало, что возможный вариант использования для isEmpty(), isFull() и sizeGuess было следующее:

//Consumer  
while(queue.isEmpty()) {} // spin until producer writes 
use_queue(); // At this point, the writes from producer _should_ be visible 

Именно поэтому они объяснили, что std::memory_order_relaxed не было бы целесообразно и std::memory_order_consume бы. Однако это верно, потому что std::memory_order_consume рекламируется до std::memory_order_acquire для всех компиляторов, о которых я знаю. Поэтому, хотя std::memory_order_consume может показаться обеспечивающей правильную синхронизацию, достаточно ввести в заблуждение оставить это в коде и предположить, что он останется верным, особенно если std::memory_order_consume должны были быть реализованы по назначению. Вышеупомянутый прецедент не сможет работать с более слабыми архитектурами, поскольку соответствующая синхронизация не будет сгенерирована.

Имейте в виду, что это действительно необходимо сделать эти нагрузки std::memory_order_acquire, чтобы это работало по назначению, поэтому я подал this other pull request несколько дней назад. С другой стороны, они могли бы принять приобретаемую-нагрузку из петли и использовать забор в конце:

//Consumer  
while(queue.isEmpty()) {} // spin until producer writes using relaxed loads 
std::atomic_thread_fence(std::memory_order_acquire); 
use_queue(); // At this point, the writes from producer _should_ be visible 

В любом случае, std::memory_order_consume неправильно используются здесь.

+0

Спасибо, что нашли время ответить! Кстати, я как раз собирался отправить им электронное письмо прямо перед тем, как вы ответили. Таким образом, это делает более ощутимым, даже если реализация неверна, как определено стандартом. Еще довольно странная гарантия для метода isEmpty() для меня. – SideEffects

+0

Я согласен, семантика 'потребляет 'на такой вид функции кажется неуместным. У Boost даже есть свои эквивалентные функции, используя 'relaxed'. Я рад, что кто-то еще нашел это, я знал, что не могу быть единственным! – Alejandro

Смежные вопросы