通过消息传递进行通信的软件模块的"多进程"与"单进程多线程"

Ben*_*ahi 6 multithreading middleware message-queue multiprocessing inter-process-communicat

我们需要构建一个软件框架(或中间件),以便在单个机器上运行的不同软件组件(或模块)之间实现消息传递.该框架将提供以下功能:

  • 模块之间的通信是通过"消息传递".
  • 每个模块都有自己的消息队列和消息处理程序线程,它将同步处理每个传入消息.

根据上述要求,以下哪种方法是正确的(有其推理)?:

  1. 将模块实现为进程,并通过共享内存进行消息传递
  2. 在单个进程中将模块实现为线程,并通过将消息对象推送到目标模块的消息队列来进行消息传递.

有消息来源,有一些明显的缺点和优点:

  • 在Option-2中,如果一个模块导致分段错误,则该过程(因此整个应用程序)将崩溃.并且一个模块可以直接访问/改变另一个模块的内存,这可能导致难以调试的运行时错误.
  • 但是使用Option-1,您需要处理需要通信的模块刚刚崩溃的状态.如果软件中有N个模块,则系统中可能存在2 ^ N个活动/崩溃状态,这些状态会影响模块上运行的算法.
  • 同样在Option-1中,发送方不能假设接收方已收到消息,因为它可能在那一刻崩溃了.(但是系统可以警告所有模块特定模块已经崩溃;这样,发送方可以断定接收方将无法处理该消息,即使它已成功接收到该消息)

我赞成Option-2,但我不确定我的论点是否足够稳固.你有什么看法?

编辑:根据要求澄清,这里有更多的规格细节:

  • 这是一个将在Linux OS上运行的嵌入式应用程序.
  • 不幸的是,我无法告诉你项目本身,但我可以说项目有多个组件,每个组件都将由自己的团队(3-4人)开发,并决定这些组件之间的通信组件/模块是通过某种消息传递框架.
  • C/C++将用作编程语言.
  • "模块接口API"将自动提供给模块开发人员的是:(1)消息/事件处理程序线程循环,(2)同步消息队列,(3)函数指针成员变量,您可以在其中设置消息处理函数.

Ben*_*ahi 6

这是我能想到的:

多进程(1)与单进程,多线程(2):

  • 分段故障的影响:(2)中,如果一个模块导致分段故障,整个应用程序崩溃.在(1)中,模块具有不同的存储区域,因此只有导致分段错误的模块才会崩溃.
  • 邮件传递保证:(2)中,您可以假设邮件传递得到保证.在(1)中,接收模块可能在接收之前或在处理消息期间崩溃.
  • 在模块之间共享内存:在(2)中,整个内存由所有模块共享,因此您可以直接发送消息对象.在(1)中,您需要在模块之间使用"共享内存".
  • 消息传递实现:(2)中,您可以在模块之间发送消息对象,在(1)中您需要使用网络套接字,unix套接字,管道或存储在共享内存中的消息对象.为了提高效率,将消息对象存储在共享内存中似乎是最佳选择.
  • 模块之间的指针使用:在(2)中,您可以在消息对象中使用指针.堆对象的所有权(由消息中的指针访问)可以传输到接收模块.在(1)中,您需要在"共享内存"区域中手动管理内存(使用自定义malloc/free函数).
  • 模块管理:在(2)中,您只管理一个流程.在(1)中,您需要管理每个代表一个模块的进程池.


baz*_*zza 3

听起来您正在实施通信顺序流程。出色的!

首先处理线程与进程,我会坚持使用线程;上下文切换时间更快(特别是在 Windows 上,进程上下文切换非常慢)。

其次,共享内存与消息队列;如果您正在进行完全同步消息传递,那么对性能没有影响。共享内存方法涉及一个共享缓冲区,该缓冲区由发送方复制到该共享缓冲区,并由读取方从中复制。这与消息队列所需的工作量相同。因此,为了简单起见,我会坚持使用消息队列。

事实上,您可能想考虑使用管道而不是消息队列。您必须编写代码来使管道同步(它们通常是异步的,这将是 Actor 模型;消息队列通常可以设置为零长度,这可以实现您想要的同步和正确的 CSP),但是然后您可以很容易地使用套接字代替。如果需要的话,您的程序可以成为多机分布式程序,但您根本不必更改架构。进程之间的命名管道也是一个等效的选项,因此在进程上下文切换时间良好的平台(例如Linux)上,整个线程与进程的问题就消失了。因此,更加努力地使用管道可以为您提供非常重要的可扩展性选项。

关于崩溃;如果您采用多进程路线并且希望能够优雅地处理进程的失败,您将需要做一些工作。本质上,您将需要在消息传递通道的每一端都有一个线程,只是为了监视另一端的响应能力(可能通过在它们之间来回反弹保持唤醒消息)。这些线程需要将状态信息提供给相应的主线程,以告知另一端何时未能按计划发送保持唤醒消息。然后主线程可以采取相应的行动。当我这样做时,我让监视器线程在可能的情况下自动重新连接(例如,远程进程已恢复正常),并告诉主线程。这意味着我的系统的某些部分可以来来去去,而其余部分则可以很好地应对。

最后,您的实际应用程序进程将结束为一个循环,顶部有类似 select() 的东西,等待来自它期望听到的所有不同通道(和监视线程)的消息输入。

顺便说一句,这种事情在 Windows 中实现起来非常困难。在任何 Microsoft 语言中,都没有与 select() 完全等效的函数。套接字有一个 select(),但你不能像在 Unix 中那样在管道等上使用它。Cygwin 的人在实现他们的 select() 版本时遇到了真正的问题。我认为他们最终为每个文件描述符提供了一个轮询线程;效率极低。

祝你好运!