многопоточность — буферный механизм Writer / Reader для большого размера — высокая частота переполнения стека

Мне нужен механизм с одним писателем и несколькими читателями (до 5), который писатель выталкивает данные размером почти 1 МБ каждый и 15 пакетов в секунду непрерывно, что будет записано в c ++. Я пытаюсь сделать так, чтобы один поток продолжал записывать данные, в то время как 5 читателей будут выполнять некоторые операции поиска в соответствии с отметкой времени данных одновременно. Я должен хранить каждый пакет данных 60 минут, а затем они могут быть удалены из контейнера.

Поскольку данные могут расти как 15 МБ * 60 с * 60 мин = 54000 МБ / ч, мне нужно почти 50 ГБ места для хранения данных и выполнения операций достаточно быстро как для автора, так и для читателя. Но дело в том, что мы не можем хранить данные такого размера в кеше или оперативной памяти, поэтому они должны быть на жестком диске, таком как SSD (жесткий диск будет слишком медленным для такой операции)

До сих пор я думал о том, чтобы создать кольцевой буфер (так как я могу рассчитать максимальный размер), непосредственно внедренный в SSD, который я до сих пор не смог найти подходящий пример, и я не знаю, возможно, либо нет, либо реализовывать какой-то механизм отображения, что в ОЗУ будет доступен один круговой массив, который просто хранит временные метки данных и физический адрес памяти для поиска данных, доступных на жестком диске. , Так что, по крайней мере, поиск будет быстрее.

Так как блокировка, мьютекс или семафор любого типа будут замедлять операции (особенно важна запись, мы не можем потерять данные из-за какой-либо операции чтения), я не хочу их использовать. Я знаю, что есть некоторые общие блокировки, но я думаю, что у них есть некоторые недостатки. Есть ли способ / идея реализовать такую ​​систему с блокировкой без ожидания, без ожидания и с поточной безопасностью? Любая структура данных (контейнер), шаблон, пример кода / проекта или другие предложения будут высоко оценены, спасибо …

РЕДАКТИРОВАТЬ: Есть ли другая идея, а не больший объем оперативной памяти?

4

Решение

Это может быть сделано на обычном ПК (и может масштабироваться до сервера без изменения кода).

Замки не проблема. С одним писателем и несколькими потребителями, выполняющими трудоемкие задачи с большими данными, у вас будет редкая блокировка и практически нулевая конкуренция за блокировку, так что это не проблема.
Что-нибудь из простого спин-блокировки (если вы действительно отчаянно нуждаетесь в низкой задержке) или предпочтительно pthread_mutex (во всяком случае, в большинстве случаев это будет спин-блокировка). Ничего фантастического.

Обратите внимание, что вы не делайте получить блокировку, получить мегабайт данных из сокета, записать их на диск, а затем снять блокировку. Это не так, как это работает.
Вы получаете мегабайт данных и записываете их в регион что вы владеете исключительно, затем получить блокировку, изменить указатель (и, таким образом, передать право собственности) и снимите блокировку. Замок защищает метаданные, не каждый байт в буфере размером в гигабайт. Длительные задачи, короткое время блокировки, конфликт = ноль.

Что касается фактических данных, запись 15MiB / s абсолютно не проблема, нормальный жесткий диск будет делать в 5-6 раз больше, а SSD легко сделает это в 10-20 раз. Это также не то, что вам даже нужно делать самостоятельно. Это то, что вы можете оставить операционной системе для управления.

Я хотел бы создать 54,1 ГБ1 файл на диске и карта памяти (если предположить, что это 64-битная система, разумное предположение, если говорить о серверах с несколькими гигабайтами оперативной памяти, это не проблема). Операционная система позаботится обо всем остальном. Вы просто записываете свои данные в отображенную область, которую используете в качестве кругового буфера.2.
То, что было написано совсем недавно, будет более или менее гарантировано3 быть резидентом оперативной памяти, чтобы потребители могли получить к ней доступ без сбоев. Старые данные могут находиться или не находиться в ОЗУ, в зависимости от того, достаточно ли на вашем сервере физической памяти.

К более старым данным все еще можно получить доступ, но, вероятно, с несколько меньшей скоростью (если не хватает физической оперативной памяти для сохранения резидентного набора). Это, однако, не повлияет на производителя или потребителей, читающих недавно записанные данные (если машина не обладает такими ужасными характеристиками, что даже не может вместить 2-3 ваших блока объемом 1 МБ в ОЗУ, но тогда у вас другая проблема! ).

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

В любом случае вам необходимо учитывать область (либо в виде указателя, либо лучше в качестве смещения в отображении) данных в сопоставленном кольцевом буфере, которая является «допустимой», и область, которая «не используется».
Производитель является владельцем отображения, и он «позволяет» потребителям получать доступ к данным в пределах, указанных в метаданных (пара смещений начала / конца). Только производитель может изменить эти метаданные.
Любой (включая производителя), получающий доступ к этим метаданным, должен получить блокировку.

Вероятно, даже возможно сделать это с атомарными операциями, но, видя, как вы блокируете редко, я бы даже не стал беспокоиться. Использование блокировки не составляет никакого труда, и нет никаких тонких ошибок, которые вы можете сделать.

Поскольку производитель знает, что потребители будут смотреть на данные только в пределах четко определенных границ, он может записывать в области за пределами границ (область, известная как «emtpy») без блокировки. Это нужно только заблокировать, чтобы изменить границы впоследствии.

Если 54.1Gib> 54Gib, у вас есть сто резервных блоков по 1 МБ в отображении, которые вы можете записать. Это, вероятно, намного больше, чем нужно (2 или 3 должны сделать), но это не помешает иметь несколько дополнительных. Когда вы пишете в новый блок (и увеличиваете допустимый диапазон на 1), также настраивайте другой конец «допустимого диапазона». Таким образом, потокам больше не будет разрешен доступ к старому блоку, но поток, все еще работающий в этом блоке, может завершить свою работу (данные все еще существуют).
Если кто-то строго следит за правильностью, это может создать состояние гонки, если обработка блока занимает очень много времени (в этом случае более 1 1/2 минуты). Если вы хотите быть абсолютно уверенным, вам понадобится еще одна блокировка, которая в худшем случае может заблокировать производителя. Это то, чего вы абсолютно не хотели, но блокировка производителя в худшем случае — единственное, что на 100% правильно в каждом надуманном случае, если только у гипотетического компьютера нет неограниченной памяти.
Учитывая ситуацию, я думаю, что эта теоретическая гонка является «допустимой» вещью. Если обработка одного блока действительно занимает так много времени с постоянным поступлением большого количества данных, у вас есть гораздо более серьезная проблема под рукой, поэтому практически, это не проблема.

Если ваш босс в какой-то момент в будущем решит, что вы должны оставить более 1 часа невыполненных заданий, вы можете увеличить файл и переназначить его, и когда «пустая» область будет следующей в конце размера старого буфера, просто увеличьте «известный» размер файла и настройте значение max_size в производителе. Потребительские темы даже не должны знать. Конечно, вы могли бы создать еще один файл, скопировать данные, поменять местами и заблокировать потребителей, но я считаю, что это плохое решение. Вероятно, нет необходимости немедленно видеть увеличение размера, но с другой стороны, очень желательно, чтобы это был «невидимый» процесс.
Если вы поместите больше оперативной памяти в компьютер, ваша программа будет «волшебным образом» использовать его, без необходимости что-либо менять. Операционная система просто будет хранить больше страниц в оперативной памяти. Если вы добавите еще несколько потребителей, он будет работать так же.


1 Преднамеренно больше, чем нужно, пусть будет несколько «лишних» блоков по 1 МБ.

2 Предпочтительно, вы можете madvise операционная система (если вы используете систему с деструктивной подсказкой DONT_NEED, например, Linux), которая вас больше не интересует перед перезаписью региона. Но если вы этого не сделаете, это будет работать в любом случае, только немного менее эффективно, потому что ОС, возможно, будет выполнять операцию чтения-изменения-записи, когда операции записи было бы достаточно.

3 Там, конечно, никогда действительно гарантия, но это так и будет.

3

Другие решения

54 ГБ / час = 15 МБ / с. Хороший SSD в наши дни может записать 300+ МБ / с. Если вы держите 1 час в оперативной памяти, а затем время от времени сбрасываете старые данные на диск, вы сможете обрабатывать в 10 раз больше, чем 15 МБ / с (при условии, что ваш алгоритм поиска достаточно быстр, чтобы не отставать).

Что касается механизма быстрой блокировки между вашими потоками, я бы посоветовал заглянуть в RCU — Read-Copy Update. Ядро Linux в настоящее время использует его для достижения очень эффективной блокировки.

0

У вас есть минимальные требования к оборудованию? Память в 54 ГБ вполне возможна в наши дни (многие материнские платы могут занимать 4×16 ГБ в наши дни, и это даже не серверное оборудование). Так если Вы хотите использовать твердотельный накопитель, возможно, вам может потребоваться много оперативной памяти и круговой буфер в памяти, как вы предлагаете.

Кроме того, если в данных имеется достаточная избыточность, может оказаться целесообразным использовать некоторые дешевые алгоритмы сжатия (те, которые просты для ЦП, т. Е. Какое-то сжатие «уровня 0»). То есть Вы сохраняете не сырые данные, а какой-то сжатый формат (и, возможно, некоторый индекс), который распаковывается читателями.

0

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

0