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

时间:2013-10-03 10:35:41

标签: multithreading multiprocessing message-queue middleware inter-process-communicat

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

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

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

  1. 将模块实现为流程,并通过共享内存进行消息传递
  2. 在单个进程中将模块实现为线程,并通过将消息对象推送到目标模块的消息队列来进行消息传递。
  3. 在消息来源方面,有一些明显的缺点和优点。优点:

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

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

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

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

3 个答案:

答案 0 :(得分:5)

以下是我能想到的:

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

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

答案 1 :(得分:1)

听起来您正在实施通信顺序进程。优良!

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

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

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

关于崩溃;如果你进入多进程路由并希望能够优雅地处理进程的失败,那么你将不得不做一些工作。基本上,你需要在消息传递通道的每一端都有一个线程来监视另一端的响应(可能是通过在它们之间来回反弹保持唤醒消息)。这些线程需要将状态信息提供给它们相应的主线程,以便在另一端未按时发送保持唤醒时告诉它。然后主线程可以相应地行动。当我这样做时,我让监视器线程自动重新连接(例如远程进程已经恢复生命),并告诉主线程。这意味着我的系统中的各个部分可以来来去去,其余部分只是很好地应对。

最后,您的实际应用程序进程将最终作为循环,顶部的select()等待来自所有不同通道(和监视器线程)的消息输入。

顺便说一下,在Windows中实现这种事情是令人沮丧的。在任何Microsoft语言中,任何地方都没有恰当的select()。有一个用于套接字的select(),但你不能像在Unix中那样在管道等上使用它。 Cygwin的家伙在实现他们的select()版本时遇到了实际问题。我认为他们最终得到了每个文件描述符的轮询线程;效率极低。

祝你好运!

答案 2 :(得分:0)

您的问题缺乏对“模块”如何实施以及它们的作用的描述,并且可能描述了您计划实施所有这些的环境。

例如:

  • 如果模块本身有一些要求使得它们难以作为线程实现(例如,它们使用非线程安全的第三方库,具有全局变量等),那么您的消息传递系统也将无法通过线程实现
  • 如果您使用的是Python之类的环境,它不能很好地处理线程并行性(因为它的全局解释器锁定),并且在Linux上运行,那么线程优先于进程就不会获得任何性能优势。

还有更多事情需要考虑。如果您只是在模块之间传递数据,谁说您的系统需要使用多个线程或多个进程?还有其他架构在没有它们的情况下执行相同的操作,例如使用回调事件驱动(消息接收器可以向系统注册回调,在消息生成器生成消息时调用)。在并行性不重要且可以在调用者的执行上下文中调用接收代码的情况下,这种方法绝对是最快的。

tl;博士:你的问题只是表面上的问题:)