Многопроцессорная и однопроцессорная многопоточность для программных модулей, обменивающихся сообщениями

Нам нужно создать программную среду (или промежуточное ПО), которая позволит обмениваться сообщениями между различными программными компонентами (или модулями), работающими на одной машине. Этот фреймворк предоставит такие возможности:

  • Связь между модулями осуществляется через «обмен сообщениями».
  • Каждый модуль будет иметь свою собственную очередь сообщений и поток обработчика сообщений, который будет синхронно обрабатывать каждое входящее сообщение.

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

  1. Реализация модулей в виде процессов и обмен сообщениями через разделяемую память
  2. Реализация модулей в виде потоков в одном процессе и обмен сообщениями путем помещения объектов сообщений в очередь сообщений целевого модуля.

Из источника, есть некоторые очевидные минусы и плюсы:

  • В варианте 2, если один модуль вызывает ошибку сегментации, процесс (таким образом, все приложение) аварийно завершает работу. И один модуль может напрямую обращаться/изменять память другого модуля, что может привести к трудным для отладки ошибкам времени выполнения.
  • Но с Вариантом-1 вам нужно позаботиться о состояниях, когда модуль, с которым вам нужно связаться, только что вышел из строя. Если в программном обеспечении есть N модулей, может быть 2 ^ N многих рабочих / аварийных состояний системы, которые влияют на алгоритмы, работающие в модулях.
  • Опять же, в варианте 1 отправитель не может предполагать, что получатель получил сообщение, потому что в этот момент могло произойти сбой. (Но система может оповестить все модули о сбое конкретного модуля; таким образом, отправитель может сделать вывод, что получатель не сможет обработать сообщение, даже если он успешно его получил)

Я за вариант-2, но не уверен, достаточно ли убедительны мои аргументы. Каковы ваши мнения?

РЕДАКТИРОВАТЬ: По запросам на разъяснение, вот более подробная информация о спецификации:

  • Это встроенное приложение, которое будет работать в ОС Linux.
  • К сожалению, я не могу рассказать о самом проекте, но могу сказать, что компонентов проекта несколько, каждый компонент будет разрабатываться своей командой (из 3-4 человек), и решено, что коммуникация между этими компоненты/модули через какую-то структуру обмена сообщениями.
  • В качестве языка программирования будет использоваться C/C++.
  • Что «API интерфейса модуля» автоматически предоставит разработчикам модуля: (1) цикл обработки сообщений/событий, (2) синхронную очередь сообщений, (3) переменную-член указателя функции, где вы можете установить свой функция обработки сообщений.

person Benji Mizrahi    schedule 03.10.2013    source источник
comment
Итак, вы пытаетесь разработать структуру модулей в соответствии с требованиями уровня коммуникации. Есть ли что-нибудь еще, что эти модули должны делать, кроме связи?   -  person Alex F    schedule 03.10.2013
comment
Да, структура модуля будет сформирована в соответствии с тем, что предусмотрено коммуникационной структурой. Эти модули будут выполнять задачи от связи через сетевые сокеты до запуска алгоритмов машинного обучения. Допускается, чтобы один модуль запускал несколько потоков для собственных нужд. Но межмодульное взаимодействие будет осуществляться посредством обмена сообщениями синхронизированным образом. Дайте мне знать, если вам нужно больше разъяснений.   -  person Benji Mizrahi    schedule 03.10.2013
comment
В первую очередь следует беспокоиться о финансовых потерях для компании в случае нарушения segfault / доступа или чего-либо еще, что приведет к сбою процесса. Деловые интересы — это причина, по которой вы пишете код. На основе чистого программирования вы предполагаете, что просто находите любые ошибки и исправляете их. Я бы использовал общую память для нескольких процессов только для любого большого объема данных, которые необходимо будет загружать и совместно использовать между вашими процессами и читать напрямую, и в идеале они должны быть доступны только для чтения или управляться через базу данных.   -  person CashCow    schedule 07.08.2014


Ответы (3)


Вот что я смог придумать:

Многопроцессорный(1) и однопроцессный, многопоточный(2):

  • Влияние ошибок сегментации: в (2), если один модуль вызывает ошибку сегментации, происходит сбой всего приложения. В (1) модули имеют разные области памяти, и, таким образом, произойдет сбой только модуля, вызвавшего ошибку сегментации.
  • Гарантия доставки сообщения. В (2) вы можете предположить, что доставка сообщения гарантирована. В (1) принимающий модуль может дать сбой перед получением или во время обработки сообщения.
  • Совместное использование памяти между модулями. В (2) вся память совместно используется всеми модулями, поэтому вы можете напрямую отправлять объекты сообщений. В (1) вам необходимо использовать «общую память» между модулями.
  • Реализация обмена сообщениями: в (2) вы можете отправлять объекты сообщений между модулями, в (1) вам нужно использовать любой из сетевых сокетов, сокет unix, каналы или объекты сообщений, хранящиеся в общей памяти. С точки зрения эффективности хранение объектов сообщений в общей памяти кажется лучшим выбором.
  • Использование указателя между модулями. В (2) вы можете использовать указатели в своих объектах сообщений. Право собственности на объекты кучи (доступ к которым осуществляется с помощью указателей в сообщениях) может быть передано принимающему модулю. В (1) вам необходимо вручную управлять памятью (с помощью пользовательских функций malloc/free) в области «Общая память».
  • Управление модулями. В (2) вы управляете только одним процессом. В (1) вам необходимо управлять пулом процессов, каждый из которых представляет один модуль.
person Benji Mizrahi    schedule 04.10.2013

Похоже, вы реализуете коммуникативные последовательные процессы. Превосходно!

Прежде всего, рассматривая потоки и процессы, я бы придерживался потоков; время переключения контекста быстрее (особенно в Windows, где переключение контекста процесса происходит довольно медленно).

Во-вторых, разделяемая память и очередь сообщений; если вы выполняете полную синхронную передачу сообщений, это не повлияет на производительность. Подход с общей памятью включает в себя общий буфер, который копируется отправителем и копируется читателем. Это тот же объем работы, который требуется для очереди сообщений. Поэтому для простоты я бы придерживался очереди сообщений.

на самом деле вы могли бы рассмотреть возможность использования канала вместо очереди сообщений. Вы должны написать код, чтобы сделать канал синхронным (обычно они асинхронны, что было бы моделью акторов; очереди сообщений часто могут быть установлены на нулевую длину, что делает то, что вы хотите, чтобы он был синхронным и правильно CSP), но тогда вы может так же легко использовать сокет вместо этого. Затем ваша программа может стать распределенной на несколько машин, если возникнет такая необходимость, но вам вообще не нужно менять архитектуру. Также именованные каналы между процессами являются эквивалентным вариантом, поэтому на платформах, где время переключения контекста процесса хорошее (например, Linux), вопрос о потоке и процессе отпадает. Таким образом, работая немного усерднее, чтобы использовать канал, вы получаете очень значительные возможности масштабирования.

Что касается сбоев; если вы идете по многопроцессорному маршруту и ​​хотите иметь возможность изящно обрабатывать сбои процесса, вам придется немного поработать. По сути, вам понадобится поток на каждом конце канала обмена сообщениями просто для отслеживания реакции на другом конце (возможно, путем пересылки сообщения о пробуждении туда и обратно между собой). Эти потоки должны передать информацию о состоянии в соответствующий основной поток, чтобы сообщить ему, когда другой конец не смог отправить подтверждение бодрствования по расписанию. Затем основной поток может действовать соответствующим образом. Когда я сделал это, поток монитора автоматически переподключился, как и когда он мог (например, удаленный процесс вернулся к жизни), и также сообщил об этом основному потоку. Это означает, что части моей системы могут приходить и уходить, а остальная часть просто прекрасно справляется.

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

Между прочим, в Windows такие вещи очень сложно реализовать. В любом языке Microsoft просто нет надлежащего эквивалента select(). Для сокетов есть select(), но вы не можете использовать его для конвейеров и т. д., как в Unix. У ребят из Cygwin были серьезные проблемы с реализацией их версии select(). Я думаю, что они закончили с потоком опроса для каждого файлового дескриптора; массово неэффективно.

Удачи!

person bazza    schedule 03.10.2013
comment
Вы говорите, что подход с общей памятью включает в себя общий буфер, который копируется отправителем и копируется читателем, но содержимое сообщения может быть указателем на другую ячейку памяти в общей памяти. - person Benji Mizrahi; 04.10.2013
comment
@BenjiMizrahi, извините за очень медленный ответ - 6 лет. Да, вы действительно можете это сделать, но тогда у вас возникнет вопрос о контроле прав собственности и о том, кому разрешено изменять общую память. Отправляя целую копию — либо через канал, либо через очередь сообщений, вам не нужно об этом беспокоиться. Если отправитель изменяет свои исходные данные и получатель должен знать об этом, необходимо отправить еще одну копию (или дельты). И да, это может быть неэффективно. С тех пор как я написал этот ответ, ZeroMQ прошел долгий путь, и это то, что я бы использовал сегодня, если это вообще возможно, - person bazza; 21.12.2019

В вашем вопросе отсутствует описание того, как реализованы «модули» и что они делают, и, возможно, описание среды, в которой вы планируете все это реализовать.

Например:

  • Если к самим модулям предъявляются некоторые требования, которые затрудняют их реализацию в виде потоков (например, они используют сторонние библиотеки, не поддерживающие потоки, имеют глобальные переменные и т. д.), ваша система доставки сообщений также не может быть реализована с помощью потоков.
  • Если вы используете такую ​​среду, как Python, которая не очень хорошо обрабатывает параллелизм потоков (из-за его глобальной блокировки интерпретатора) и работаете в Linux, вы не получите никаких преимуществ производительности с потоками по сравнению с процессами.

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

tl;dr: вы только поцарапали поверхность своим вопросом :)

person Ivan Voras    schedule 04.10.2013
comment
Я сделал некоторые разъяснения по своему вопросу после вашего отзыва. То, что предоставит API интерфейса модуля, именно то, что вы упомянули: обмен сообщениями, управляемый событиями, с обратными вызовами. Мой вопрос заключается в том, используется ли он в многопоточном режиме с одним процессом (каждый поток является основным циклом этого механизма обработки событий) или в многопроцессном режиме (основной поток каждого процесса отвечает за обработку событий)? - person Benji Mizrahi; 04.10.2013
comment
Если вы общаетесь между процессами, рассматривали ли вы возможность использования чего-то другого, кроме общей памяти, например. трубы и розетки? Таким образом, вы получите более простое (все же не тривиальное) указание на то, произошел ли сбой получателя: отправитель получит сообщение об ошибке, если попытается отправить еще раз. Поскольку вы не доверяете своим модулям (вы обеспокоены тем, что потоки портят память друг друга — ваш аргумент «за» и «против» № 3), подход с несколькими процессами — единственный, который может решить эту проблему. - person Ivan Voras; 05.10.2013
comment
Если я использую сокет или каналы, мне нужно сериализовать/десериализовать объекты сообщений. Используя общую память, я могу хранить объекты сообщений в общей памяти и передавать указатель на объекты сообщений (эти указатели можно отправлять через каналы или сокеты). - person Benji Mizrahi; 05.10.2013
comment
Если он находится в разделяемой памяти, это означает, что либо а) это непрерывный фрагмент данных, структура без указателей в нем, либо б) вы полагаетесь на то, что фрагмент разделяемой памяти будет находиться по одному и тому же адресу во всех процессах ( что может быть правдой, если вы разветвляете процессы главного процесса после настройки общей памяти). Если а) верно, то вам не нужна специальная сериализация, вы можете просто отправить необработанные данные. Это просто идея; Я думаю, вы рассмотрели все основания: если вам нужна производительность, используйте потоки, если вам нужна изоляция (например, для безопасности), используйте процессы. - person Ivan Voras; 05.10.2013