У меня есть RingBuffer, который обслуживает одного потребителя и одного производителя и использует два целых числа для обнаружения новых данных:
_lastReadIndex
_lastWrittenIndex
поэтому в кольцевом буфере есть непрочитанные данные, когда эти два значения не равны.
Продюсер приращений (и модули с размером кольцевого буфера, чтобы обернуть) _lastWrittenIndex
когда элемент добавлен в кольцевой буфер.
Потребительские спины, чтение оба значения, проверка на новые данные, и когда есть, это будет приращение (и модуль) _lastReadIndex
,
Три выделенных термина подчеркивают требования в отношении многопоточности и барьеров памяти.
Насколько я могу уменьшить порядок памяти для этой конструкции, учитывая модель памяти Intel? Я считаю, что модель памяти Intel позволяет переупорядочивать нагрузки с более ранними магазинами по разным адресам?
РЕДАКТИРОВАТЬ с использованием атомарной библиотеки C ++ 11 std::memory_order_xxxx
так далее
Несколько вещей, которые вы должны сделать прежде всего:
Модуль чтения и письма, но держать _lastReadIndex
а также _lastWrittenIndex
неповрежденным, чтобы знать, сколько данных у вас есть, сколько потеряно или может заблокировать устройство записи, если оно переполняет читателя после полного цикла.
И, что очень важно, избегайте как можно большего совместного использования — поместите переменные для чтения и записи в отдельные строки кэша.
Теперь к вашему вопросу:
Если вы пытаетесь быть переносимым, порядок памяти, который вам требуется в вашем коде, не должен учитывать архитектуру. Стандартные атомарные функции могут позаботиться об этом.
Вам нужно только убедиться, что данные доступны в буфере, прежде чем увеличивать индекс записи, что означает семантику освобождения приращения.
Вы также должны убедиться, что записывающее устройство записывает данные в память и не оптимизировано, чтобы оставаться только в регистрах.
newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
atomic_store( &_lastWrittenIndex, newIndex, memory_order_release );
На x86 / 64 это будет так же, как:
newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
// release semantics means reorder barrier before action:
barrier(); // translates to `asm volatile("":::"memory");`
*(volatile int*)_lastWrittenIndex = newIndex;
При написании кода, который обращается _lastWrittenIndex
не более чем абсолютно необходимо, как указано выше, вы также можете объявить его изменчивым, но имейте в виду, что барьер все еще необходим!
Других решений пока нет …