LINUX.ORG.RU

Накостылить IPC

 , ,


0

3

Ребята, нужно придумать эффективную схему inter process communication. Имеются несколько процессов (назовем их slaves), которые общаются с одним главным процессом (назовем master). Подчеркиваю - речь о процессах, а не о потоках. Между master и slaves гоняются весьма большие куски данных. FIFO отмел ввиду тормознутости, остановился на shared memory. И вот тут нужно по уму сделать. У меня примерно такая схема родилась

https://pasteboard.co/JKcwhO7.jpg

Идея в следующем подгружаю в пространство master и slaves модуль, он состоит из таких вот блоков для передачи инфы, таких блоков (на картинке изображен один блок) штук 10 всего. Работает примерно так: slave подходит к блоку, берет семафор slave-slave, который запрещает доступ к блоку для других slave (возможно откажусь вовсе от slave-slave семафора и за каждым slave будет закреплен свой блок). Далее берет семафор slave-master, пишет в общую память, освобождает slave-master семафор, подает сингал через fifo master’у о готовности данных для чтения, подключается к fifo для ожидания сигнала о наличии ответа в общей памяти, берет slave-master семафор, читает, уходит. Как вам в целом схема? Можете предложить лучше? Насколько разумно использовать fifo в качестве condition variable? Они весьма тормознутые (shared memory быстрее раз в сто, если не ошибаюсь), возможно там и пробуждение процесса по сигналу из него тоже не на высоте (а куда торопиться, если они и так тормознутые), может можно чем-то заменить?

★★

Вначале хотел сделать «проще» и отказаться от master, использовать shared memory для хранения сложных цппшных данных в нем (map и все такое), алокаторами выделять в нем же память для контейнеров, модуль общий подгружать по одним адресам (для сохранения состояния после презапусков). Но просто чувствую, что тону в сложности, которая вытекает из такой вот «простоты».

pavlick ★★ ()

Имеются несколько процессов (назовем их slaves), которые общаются с одним главным процессом (назовем master)

Линус категорически не одобряет

man-from-36 ()

а по поводу производительности тут обычная штука: замерять, искать узкие места..

zerhud ()
Ответ на: комментарий от man-from-36

Так веб фреемворки зачем-то делают многопроцессорными, почему нельзя обрабатывать подключения многопоточно, проблема бы стала проще на порядок.

pavlick ★★ ()
Ответ на: комментарий от pavlick

почему нельзя обрабатывать подключения многопоточно, проблема бы стала проще на порядок.

Многопоточный код обычно намного сложнее, чем многопроцессный, зачастую медленнее. Как правило, если не надо шарить много данных между воркерами, лучше использовать форки (пул форков), а не треды.

Binkledum ()

КМК сокеты/пайпы куда проще. Но я не программист.

Насчет тормознутости - а шо, это реально узкое место? Было профилирование?

И да, общая схема мутная. Когда я такие вещи делал, в первую очередь решал кто будет ведущим (раздавать задачи) а кто ведомым.

AntonI ★★★ ()
Ответ на: комментарий от thunar

Потому что плохой многопоточный код написать куда проще чем многопроцессорный? :-)

А так, в рамках одного хоста, пофик.

AntonI ★★★ ()
Ответ на: комментарий от pavlick

почему нельзя обрабатывать подключения многопоточно

Кто сказал что нельзя? Можно обрабатывать тысячи соединений на пуле потоков в том же бусте, я так и писала.

А процессы можно завязать через брокер типа redis, очень удобно.

anonymous ()
Ответ на: комментарий от AntonI

Скорость всяких ФИФО/пайпов - несколько метров в секунду, это смешно очень, немного быстрее сокетов. Общая память раз в сто быстрее. И я ведь могу поднять несколько серверов (в рамках одной машны, с разными сетевухами), конекты будут идти сразу по нескольким адресам, ФИФО сразу идет лесом.

pavlick ★★ ()

Между master и slaves гоняются весьма большие куски данных.

данными владеет только одни процесс. остальные шлют ему функцию(Ф), с параметрами(А), которые что то с данными делают. если функцию(Ф) сделать библиотекой, тогда слать надо только параметры(А).

дальше придумываешь алгебру для Ф и паралелишь/оптимизируешь.

anonymous ()
Ответ на: комментарий от anonymous

Если оно обрабатывает подключение в одном процессе многопоточно, то большое тебе спасибо, прям камень с плеч, скоро пойду читать.

pavlick ★★ ()
Последнее исправление: pavlick (всего исправлений: 1)

Сейчас так говорить не принято.

slaves

Афро-американцы.

master

Белый суприматист.

WitcherGeralt ★★ ()
Ответ на: комментарий от pavlick

Скорость всяких ФИФО/пайпов - несколько метров в секунду

Откуда дровишки? У меня скорость копирования по ssh на 127.0.0.1 120MB/sec, в основном задержки на шифрование скорее всего. ЕМНИП пайпы тоже совсем не через диск ходят, но тестить лень.

В общем Вы не о том думаете. Shared memory конечно рулит, но с арх. точки зрения она отнюдь не самое простое решение -собственно Вы свой многопроцессорный код делает таким образом назад многопоточным, и нафига козе баян если все равно семафорами и пр фигней обмазываться приходиться?

PS

$ time cat test-file | wc -l
148752

real	0m0.999s
user	0m0.069s
sys	0m1.244s
$ du -hs test-file
1.3G   test-file
AntonI ★★★ ()
Последнее исправление: AntonI (всего исправлений: 2)
Ответ на: комментарий от anonymous

Хрен ты такую алгебу придумаешь, если нужно картинку передать. Гораздо выгоднее работать через loop интерфейс, там скорость 10 гиг в секунду.

anonymous ()
Ответ на: комментарий от pavlick

Если оно обрабатывает подключение в одном процессе многопоточно

Про то, как с помощью RESTinio обрабатывать входящие запросы асинхронно, есть отдельная статья: https://habr.com/ru/post/451728/

И есть демо-проект, который показывает, как это делать на самодельных очередях сообщений: https://github.com/Stiffstream/restinio-crud-example

eao197 ★★★★★ ()
Ответ на: комментарий от pavlick

Там вторым сообщением результаты тестов.

дык накладные расходы на каждое сообщение наверное никто не отменял? А если на каждое сообщение сокет/пайп создавать дык вааще наверное…

Из распределенного последнего что я велосипедил - вьювер с удаленным режимом работы, что бы по 100Гб расчетных данных с кластера не выкачивать а генерить картинки на месте и наружу отдавать. Очень тупо, через ssh, оболочка вьювера (которая на питоне) заходит по ssh на кластер, запускает там плюсового клиента и общается с ним через стандартный ввод/вывод. 112 строк кода на плюсах, 60 на питоне, по сравнению с локальной работой почти не лагает.

AntonI ★★★ ()
Последнее исправление: AntonI (всего исправлений: 1)

Тебя SJW за такой IPC уроют

mittorn ★★★★★ ()
Ответ на: комментарий от pavlick

What speed can be achieved over a loopback interface?

Ryzen machine using a «zero-copy transfer» (iperf3 -Z -c localhost) resulted in >38 Gb/s, вот как я и говорила.

anonymous ()
Ответ на: комментарий от anonymous
  1. залить черным, а потом посчитать количество белых пикселей. в данном случае залить черным будет левым нулём и белые пиксели можно не считать, а сразу вернуть ноль.

  2. повернуть вправо, а потом повернуть влево. обратные операции, можно ничего не делать.

  3. операции-запросы, не изменяющие картинку, можно паралелить сколько ядер есть.

это всё контрпримеры, демонстрирующие, что ты не прав.

anonymous ()
Ответ на: комментарий от anonymous

Я не удивлюсь, если завтра выяснится, что он и главную ветку в Git называет master.

rupert ★★★★★ ()

slave подходит к блоку, берет семафор slave-slave, который запрещает доступ к блоку для других slave

а зачем тут семафор, когда на блок можно просто навесить flock

Насколько разумно использовать fifo в качестве condition variable?

Торвальдс это советовал - пишешь в пайп по одному байту, и все.

Lrrr ★★★ ()

Друзья, всем спасибо, штудирую Beast, мне много проще сделать желаемое с многопоточного, чем костылить к многопроцессному (про Redis услышал, но и без него обойдусь, думаю).

pavlick ★★ ()

Попробуй message queue, если тебе нужен только Linux. Если говорить о shared memory,то у VPP есть работающая API на нем. https://github.com/vpp-dev/libmemif

xpahos ★★★★★ ()
Ответ на: комментарий от eao197

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

pavlick ★★ ()
Ответ на: комментарий от pavlick

Если это простой файлообменник, то не понятно зачем вам там многопоточность/многопроцессность. Может вы еще какие-то преобразования тяжелые над файлами делаете?

Ну и да, имеет смысл посмотреть в сторону restinio из-за наличия там поддержки sendfile из коробки.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Ну в один поток - сильно ущербно получается, а уже с двумя начинаются вопросы с разделямыми данными. В случае мнопроцессности это неприятный геморрой, а в многопоточном сервере - вполне тривиальная задача. Каких-то супер расчетов не делаю, все упрется в ширину канала. Ну и опыт какой-то будет (о применении которого уже есть планы), зачем делать что-то сильно бездарное?

Кстатит, а вообще имеет ли какой-то большой смысл запускать в asio::io_contex.run() более двух потоков? Чисто физический одна линия для приема, одна для передачи, никакой параллельности здесь не будет (при условии, что сложных задач хендлеры не решают, или выносят их в другие потоки).

pavlick ★★ ()
Последнее исправление: pavlick (всего исправлений: 1)
Ответ на: комментарий от pavlick

Ну в один поток - сильно ущербно получается, а уже с двумя начинаются вопросы с разделямыми данными.

Простите за то, что туплю, но зачем в вашей задаче (если она реально сводится к отдаче файлов) многопоточность и разделяемые данные?

Кстатит, а вообще имеет ли какой-то большой смысл запускать в asio::io_contex.run() более двух потоков?

Если у вас будут десятки тысяч соединений, то почему бы и нет?

Вообще, тут все упрется в то, насколько эффективно у вас отрабатывают completion handler-ы для операций ввода/вывода. Если не очень эффективно, то имеет смысл запускать io_context::run на пуле потоков. Тогда торможение одного completion handler-а будет компенсировано возможностью запустить несколько completion handler-ов в параллель.

А так-то нужность запуска io_context::run на пуле потоков можно проверить имитируя нужную вам нагрузку.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Простите за то, что туплю, но зачем в вашей задаче (если она реально сводится к отдаче файлов) многопоточность и разделяемые данные?

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

Вообще, тут все упрется в то, насколько эффективно у вас отрабатывают completion handler-ы для операций ввода/вывода.

сделать что-то медленнее, чем требуемое время для прередачи по сети - это еще постараться надо (SQL)?)

pavlick ★★ ()
Ответ на: комментарий от pavlick

Ну данные ведь кто-то и подгружает

Кто и откуда? Если вам нужно статику из файлов на диске отдавать, то sendfile в руки и полный вперед.

сделать что-то медленнее, чем требуемое время для прередачи по сети - это еще постараться надо

Так характер задачи вы не раскрываете. Но какие-то тяжелые вычисления у вас есть («сложных задач хендлеры не решают, или выносят их в другие потоки») раз вы их в отдельные потоки выносите. Опять же аутентификация у вас есть, непонятно, как именно она реализована.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Ну данные ведь кто-то и подгружает

Другие клиениты.

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

pavlick ★★ ()
Ответ на: комментарий от pavlick

Другие клиениты.

Т.е. вы операции записи входящих данных делегируете на другие рабочие потоки?

А всякая регистрация, аутентификация, динамическая генерация страницы (некоторых эл-ов) - это просто крохи для цпп кода по сравнению со временем на передачу по сети.

Не зная, как у вас построена аутентификация, сложно что-то сказать. Может у вас там синхронный запрос в РСУБД на другом серваке.

Опять же, если у вас 1000 i/o операций в обработке, то вы можете и не заморачиваться на время работы одного completion handler-а, мол «это просто крохи для цпп кода». А когда у вас 25K i/o операций, то чем тяжелее отдельный completion handler, тем меньше шансов обработать эти самые i/o операции вовремя. И тут может помочь запуск io_context на пуле.

не раз видел примеры, пускают рой потоков в run(), часто бессмысленно, по-моему.

Может нужно было спрашивать в каждом конкретном случае?

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Т.е. вы операции записи входящих данных делегируете на другие рабочие потоки?

Хендлеры пишут в кэш (простая std::map в памяти), другой поток периодические просыпается и обслуживает кэш (в том числе и делает запись на диск).

Может нужно было спрашивать в каждом конкретном случае?

Я ведь изначально обозначил: «(при условии, что сложных задач хендлеры не решают, или выносят их в другие потоки)». Т.е. я еще тогда допустил, что задачу для хендлера можно придумать сложную. Мой тезис был о том, что как ни крути, а данные по сети получать параллельно не начнешь. А некоторые фреемворки по умолчание запускают количество потоков/процессов-воркеров == количеству ядер. Т.е., на мой взгляд, для подавляющего числа задач (которые не являются супер сложными) по умолчанию создается избыточность, лишь лишняя трата времени на синхронизацию. Понимаю это можно сознательно уменьшить количество воркеров до 2, а не полагаться на неразумный дефолт.

pavlick ★★ ()
Ответ на: комментарий от pavlick

Хендлеры пишут в кэш (простая std::map в памяти), другой поток периодические просыпается и обслуживает кэш (в том числе и делает запись на диск).

В принципе, понятно. Хотя разделяемые данные – это зло.

Я ведь изначально обозначил

Мой поинт был такой: если вы спрашиваете «есть ли вообще смысл запускать io_context::run на нескольких потоков?», то общий ответ такой: «да, в этом может быть смысл, но нужно смотреть по месту».

Но если вас интересует почему так делали в тех проектах, на которые вы смотрели, то логично было бы спросить у авторов этих проектов. Может там смысл был, просто он был неочевиден, а проявлялся в каких-то редких ситуациях.

eao197 ★★★★★ ()
Ответ на: комментарий от pavlick

Кто тебе мешает запускать каждый io_context в отдельном потоке? Тогда синхронизация становится ненужной, все операции будут в том же самом потоке.

anonymous ()
Ответ на: комментарий от pavlick

а данные по сети получать параллельно не начнешь.

Зато можно параллельно читать и писать.

anonymous ()

Имхо автор мудак, не понимает что ему надо и не умеет дизайнить IPC.

anonymous ()
Ответ на: комментарий от anonymous

Возможно. Ну ты хоть сам набросай схему, а я поучусь.

pavlick ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.