У меня есть вопрос о том, что делать в случае медленного потребителя в разрушителе lmax, таком как кольцевой буфер, в котором есть несколько производителей и один потребитель, работающий на x86 Linux. Используя шаблон кольцевого буфера типа lmax, вы постоянно перезаписываете данные, но что, если потребитель работает медленно. Поэтому, как вы справляетесь со случаем, когда, скажем, в кольцевом буфере размером 10-9 кольцевых слотов 0-9 ваш потребитель находится в слоте 5, и теперь ваши пишущие устройства готовы начать запись слота 15, который также является слотом 5 в буфере (то есть: слот 5 = 15% 10) Каков типичный способ справиться с этим, когда авторы по-прежнему создают данные в порядке их поступления, а клиенты получают данные в том же порядке? Это действительно мой вопрос. Ниже приведены некоторые подробности о моем дизайне, и он отлично работает, просто у меня в настоящее время нет хорошего способа решить эту проблему. Есть несколько потоков, выполняющих запись, и один поток, выполняющий чтение. Я не могу представить несколько потоков чтения, не изменив существующий дизайн, который в настоящее время выходит за рамки текущего проекта, но все еще интересуется вашими мыслями, если они используют это как решение.
Особенности дизайна
У меня есть кольцевой буфер, и дизайн в настоящее время имеет несколько потоков производителей и один потребительский поток. Эта часть дизайна существует и в настоящее время не может быть изменена. Я пытаюсь удалить существующую систему очередей, используя свободный кольцевой буфер блокировки. То, что у меня есть, заключается в следующем.
Код работает на x86 Linux, для писателей работает несколько потоков, а для читателя — один поток. Читатель и писатель начинают один слот и std::atomic<uint64_t>
Таким образом, читатель начинает со слота 0, а писатель — со слота 1, затем каждый писатель сначала запрашивает слот, выполняя атомарный fetch_add(1, std::memory_order::memory_order_acq_rel)
на последовательности писателя, вызвав incrementSequence
показано ниже, а затем используйте цикл compare_and_swap, чтобы обновить последовательность считывателей, чтобы клиенты знали, что этот слот доступен, см. updateSequence
,
inline data_type incrementSequence() {
return m_sequence.fetch_add(1,std::memory_order::memory_order_seq_cst);
}void updateSequence(data_type aOld, data_type aNew) {
while ( !m_sequence.compare_exchange_weak(aOld, aNew, std::memory_order::memory_order_release, std::memory_order_relaxed)
if (sequence() < aNew) {
continue;
}
break;
}
}
inline data_type sequence() const {
return m_sequence.load(std::memory_order::memory_order_acquire);
}
Кольцевой буфер (или FIFO в целом — не обязательно должен быть реализован как кольцевой буфер) предназначен для сглаживания всплесков трафика. Несмотря на то, что производители могут представлять данные в виде пакетов, потребители могут иметь дело с постоянным потоком ввода.
Если вы переполняете FIFO, это означает одно из двух:
Для меня это звучит так, как будто вы сейчас попадаете на второе место: ваш единственный потребитель просто недостаточно быстр, чтобы не отставать от производителей. Единственный реальный выбор в этом случае состоит в том, чтобы ускорить потребление путем оптимизации одного потребителя или добавления большего количества потребителей.
Это также звуки немного, как будто ваш потребитель может оставлять свои входные данные в FIFO, пока они обрабатывают данные, так что место в FIFO остается занятым, пока потребитель отделки обрабатывая этот вход. Если это так, вы можете решить вашу проблему, просто попросив потребителя удалить входные данные из FIFO, как только он начнет обработку. Это освобождает этот слот, так что производители могут продолжать помещать ввод в буфер.
Еще один момент: создание динамического размера FIFO может быть проблемой. Проблема довольно проста: она может скрыть тот факт, что у вас действительно есть вторая проблема — нехватка ресурсов, необходимых для обработки данных на стороне потребителя.
Предполагая, что как производители, так и потребители являются пулами потоков, самый простой способ сбалансировать систему — это часто использовать FIFO фиксированного размера. Если производители начинают опережать потребителей настолько, что FIFO переполняется, производители начинают блокировать. Это позволяет пулу потребительских потоков потреблять больше вычислительных ресурсов (например, работать на большем количестве ядер), чтобы поддерживать связь с производителями. Это, однако, зависит от возможности добавить больше потребителей, не ограничивая систему одним потребителем.