目录

为什么“命名”重要?

直接通信(Direct Communication)

非对称寻址(Asymmetric Addressing)

⚠️ 问题:进程之间的强依赖 —— 低模块性(Limited Modularity)

间接通信(Indirect Communication)

邮箱 / Mailbox 是什么?

实现方式与特点 

多个接收者共享一个邮箱时,消息由谁接收?


我们现在继续讲解 消息传递系统(Message-Passing Systems) 中的通信链接实现方式,这一节的主题是: Naming(命名)——进程之间如何识别彼此来建立通信关系。

操作系统:消息传递系统(Message-Passing Systems)Part 1-CSDN博客

为什么“命名”重要?

在消息传递系统中,如果两个进程想要互相通信,它们必须能够:

  • 识别对方是谁(是谁发的消息,要发给谁);

  • 告诉操作系统消息应该发给哪个进程,或者从哪个进程接收。

这就涉及到通信中的 命名机制。

命名机制的两种基本方式:

命名方式分为两类:

 1. Direct Communication(直接通信)

 2. Indirect Communication(间接通信)


直接通信(Direct Communication)

在直接通信中:

每个进程必须明确指定对方的身份,才能进行消息传递。

使用格式:

send(P, message)      → 发送消息给进程 P  
receive(Q, message)   → 从进程 Q 接收消息

举例说明:

  • 如果有进程 A 和进程 B,A 想发消息给 B,就必须写明:“发给 B”;

  • 如果 B 想接收 A 的消息,就必须写明:“接收来自 A 的消息”。

这就好像写信时你必须知道对方的收件地址(进程名或ID),否则邮局无法投递。

通信链接的属性(在直接通信下)

在这种模式下,通信链接具有以下几个关键特性:

属性 描述
自动建立链接 只要两个进程都指定了彼此,系统就会自动建立通信链接。
一一对应(点对点) 每条通信链接只在两个进程之间建立。
唯一性 每对进程之间只能有一个通信链接(不会重复建立)。

例如:

假设系统中有进程 P1 和 P2:

  • send(P2, msg):P1 发送消息给 P2;

  • receive(P1, buffer):P2 从 P1 接收消息。

这两个调用会自动建立一条“P1 ↔ P2”之间的通信通道(由操作系统维护)。

直接通信的优点与缺点

优点 说明
简洁直接 操作明确,通信双方身份清晰
实现简单 每条通信只涉及两个确定的进程
缺点 说明
缺乏灵活性 如果有多个进程之间动态通信,不便于扩展
程序耦合高 通信双方必须提前知道对方是谁,增加程序间依赖
不适合复杂结构 对于广播、组播、多对一通信不够灵活

直接通信需要通信双方显式指定彼此的身份,操作系统会为这对进程建立一个唯一的一对一通信链接。


非对称寻址(Asymmetric Addressing)

在上节中我们讲过 对称直接通信 的形式是:

send(P, message);      // 明确指定接收者 P  
receive(Q, message);   // 明确指定发送者 Q

这种方式要求 双方都知道彼此的身份,通信链接基于“P 和 Q 互相明确”的前提建立。 

那什么是非对称的直接通信?

✅ 核心区别:在非对称直接通信 中:

  • 发送者需要指定接收者是谁;

  • 但接收者不需要知道发送者是谁。

send(P, message);               // 明确发送到进程 P  
receive(id, message);          // 接收来自任意进程的消息,并记录发送者是谁
  • id 是一个变量,用来存放实际发来消息的进程名或 ID;

  • 接收者不提前指定发送者是谁,而是在收到消息之后才知道;

  • 系统会自动将消息“交到它手上”,同时告诉它“是谁发来的”。

🧾 举个例子:

假设系统中有三个进程:A(发送者)、B(发送者)、C(接收者)

C 使用如下代码:

receive(sender_id, msg);

当 A 或 B 任意一个向 C 发消息时:

  • C 会收到消息;

  • 并自动得知发送者是 A 还是 B;

  • 不需要提前写 receive(A, msg)receive(B, msg)

这种方式的特点:非对称寻址(Asymmetric Addressing)

特点 描述
发送方仍需明确接收方 send(P, msg) 中,P 必须指定
接收方不需要知道是谁发的 receive(id, msg) 会自动捕获
增强灵活性 允许接收方处理来自多个进程的消息
适合服务端模型 比如服务器接收多个客户端发来的消息时,不必为每个客户端写一个 receive(...) 语句

在非对称的直接通信中,发送者指定目标进程,而接收者无需知道发送者是谁,收到消息后再由系统告知来源。这种方式更灵活,适合处理来自多个发送方的消息。 


继续深入直接通信中存在的一个重要问题:

⚠️ 问题:进程之间的强依赖 —— 低模块性(Limited Modularity)

无论是对称(symmetric)还是非对称(asymmetric)命名机制,它们都存在一个共同的缺点:

进程之间强烈依赖彼此的标识符(ID)或名字,这会破坏程序的模块化设计。

1. 什么是“模块化”(Modularity)?

模块化是指:

  • 每个组件(或进程)尽量独立工作;

  • 不需要知道其他组件的内部细节;

  • 当你修改一个模块时,不必去修改其他模块。

这是软件设计中非常重要的一条原则,可以提升:可维护性、可重用性、可扩展性

 2. 为什么对称 / 非对称直接通信破坏模块性?

在这两种情况下,通信双方都直接写死了对方的名字(或 ID),这会导致一个问题:

❗ 修改进程名会影响其他进程

比如你有一个进程 PaymentProcessor,其他很多进程都调用了:

send(PaymentProcessor, msg);

现在你要把它重命名为 PaymentService,那么所有写过 send(PaymentProcessor, msg) 的代码都要去修改。否则程序就会通信失败。

这显然是非常低效且易错的,特别是:

  • 在大型系统中,进程众多;

  • 或者系统需要动态加载模块;

  • 或者模块可能会被不同项目重用。

🔁 举个例子(现实类比):

这就好比你在一个公司内部,所有人都要直接记住彼此的私人手机号,才能发信息。

如果某人换了号码,你就必须手动去联系所有可能的人说“我换号了”。

这种方式效率极低,也极容易出错。

而如果公司改为发邮件到统一的邮箱(如 support@company.com),只需后台调整实际接收人,其他人都无需修改,系统就更灵活和解耦了。

🔜 预告:如何解决这个问题?

这就引出了下一节我们要讲的内容 —— Indirect Communication(间接通信)。

通过引入“邮箱(Mailbox)”或“消息通道”,可以:

  • 解除进程之间的命名依赖;

  • 提高系统的模块性和扩展性。


间接通信(Indirect Communication)

在 直接通信 中,进程必须直接知道对方的身份(名称或进程 ID),这导致了强耦合,模块之间难以独立。

间接通信的核心思路是:不要把消息发给进程本身,而是发到一个“中间人”——邮箱(Mailbox)或端口(Port)。

邮箱 / Mailbox 是什么?

Mailbox(邮箱)是一个系统中可被进程共享的消息容器。
进程可以往邮箱里放消息,也可以从邮箱中取消息。

你可以把它想象成:

  • 一个收件箱,多个进程可以向它发消息;

  • 一个缓冲区,消息暂时存放在这里,等待被处理。

发送与接收操作(与进程名无关)

在间接通信中,进程之间不直接互相引用,而是通过“邮箱”来传递消息。

✉️ 发送格式:

send(A, message);     // 向邮箱 A 发送一条消息

📥 接收格式:

receive(A, message);  // 从邮箱 A 接收一条消息
  • 注意:这里的 A 是邮箱的名字,不是某个进程。

  • 多个发送者可以同时往同一个邮箱里发消息;

  • 多个接收者也可以从这个邮箱中轮流读取消息(根据具体策略)。


实现方式与特点 

邮箱作为通信媒介,改变了原来“点对点”通信模型,使得通信变得更灵活、更广泛。以下是它的核心属性:

1. 链接必须依赖“共享邮箱”而建立

只有当两个进程都知道同一个邮箱时,它们之间才建立通信链接。

  • 如果 P 和 Q 都知道邮箱 M,那么可以:

    • P 执行 send(M, msg)

    • Q 执行 receive(M, msg)

  • 这时候,P ↔ Q 之间的通信链接通过邮箱 M 建立。

⛔ 如果两者没有共同邮箱,即使存在其他邮箱,也无法通信。

2. 一个链接可以关联多个进程(多对多通信)

与直接通信“一对一”的限制不同,邮箱允许多个进程同时通过它发送或接收消息。

这意味着:

  • 多个发送者 → 一个邮箱

  • 一个邮箱 → 多个接收者

这种模式天然支持:

  • 生产者-消费者模型

  • 服务端多线程处理模型

  • 广播型消息分发

举例:

邮箱 M 关联的进程
P1、P2 向 M 发送消息(多个发送者)
C1、C2、C3 从 M 接收消息(多个接收者)

这就构成了一个多对多的通信通道,完全解耦了发送方与接收方。

3. 两个进程之间可以通过多个邮箱建立多条通信链接

与直接通信“每对进程只有一条链接”不同,间接通信允许多个链接存在于相同进程对之间。

也就是说:

  • P 和 Q 可以共享:

    • 邮箱 M1:用于传输“控制命令”

    • 邮箱 M2:用于传输“数据内容”

    • 邮箱 M3:用于传输“错误日志”

每个邮箱代表一条独立的通信链路,用途可以不同,策略也可以不同(如缓冲大小、优先级等)。

特性 描述
建立条件 链接建立于共享邮箱的基础上,只有双方都知道该邮箱时才有效
多参与者 一个邮箱可以被多个发送者和接收者共享,支持多对多通信
多链接支持 同一对进程之间可以建立多个不同的通信链接(多个邮箱)

在间接通信中,通信链接不是建立在进程对之间,而是建立在“共享邮箱”之上,从而支持多进程协作、多条通道、逻辑分离,大幅增强系统灵活性与可扩展性。 


多个接收者共享一个邮箱时,消息由谁接收?

我们现在假设:

  • 有三个进程:P1P2P3

  • 它们都共享同一个邮箱 A

  • P1 执行 send(A, msg) —— 向邮箱 A 发送了一条消息;

  • P2P3 同时在执行 receive(A, msg) —— 想从邮箱 A 取出消息。

现在的问题是:谁会实际收到这条消息?是 P2 还是 P3

答案是:这取决于系统对“接收规则”的设计方式

我们来逐一解释系统可能采用的三种策略:

1. 每个链接只允许两端通信(双端链接)

如果系统设计为:一个邮箱一次只能连接两个进程(例如:一个发送方 + 一个接收方),那就意味着:

  • 一旦 P1P2 成为该邮箱的活跃通信对;

  • P3 将无法从这个邮箱接收消息,除非切换链接。

❗ 这种方式限制灵活性,通常不常见,但在早期系统或特殊嵌入式场景下可能存在。

2. 一次只允许一个接收者使用 receive()

如果系统设计为:一个邮箱同一时刻只能有一个进程挂起在 receive() 上,

  • 那么当 P2P3 都尝试执行 receive(A, msg) 时,系统会只允许其中一个挂起等待;

  • 另一个会被拒绝或阻塞,直到前者完成。

这种方式可以避免竞争和冲突,但也会牺牲并发性。

3. 由系统来决定由谁接收消息(调度策略)

这是最常见也最灵活的一种方式:

系统允许多个接收者同时监听一个邮箱,然后:

🌀 系统自动决定谁来接收消息,有三种可能实现:

策略 说明
任意选择(Arbitrary) 系统随机选一个接收者(如 P2 或 P3),但不能同时发给两个
调度算法(如 Round-Robin) 系统记录最近接收者,下一条消息轮换给下一个接收者
发件人控制(Sender-Aware) 系统允许发件人知道哪个进程接收了消息(有时也能指定优先接收者)

这种方式类似于“事件分发”机制,非常适合并发服务场景,比如:

  • 一个邮箱代表一个任务队列;

  • 多个 worker(P2, P3)监听邮箱;

  • 谁空闲就谁来处理。

系统策略 P1 发出消息后,P2 与 P3 的行为
限定双端 只能一对通信,另一个进程无法接收
限定单接收者挂起 只有一个进程能监听,另一个等待
系统调度 任意、轮询或指定的方式决定哪个进程接收

当多个进程共享同一个邮箱时,谁来接收消息,取决于系统对通信链接策略、并发接收管理、邮箱所有权等方面的设计 —— 大多数现代系统采用“系统调度”的方式,在多个监听者中公平或按策略分配消息接收权。 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐