操作系统:消息传递系统(Message-Passing Systems)Part 2
本文讲解了消息传递系统中的命名机制,重点对比了直接通信和间接通信两种方式。直接通信要求进程显式指定对方标识符,包括对称和非对称两种形式,但存在强耦合、模块化程度低的问题。间接通信通过引入邮箱(Mailbox)作为中间媒介,允许进程不直接依赖对方身份,支持多对多通信模式,提高了系统的灵活性和可扩展性。文章还分析了邮箱通信的实现特点,包括链接建立条件、多进程共享规则以及消息接收策略等,指出系统调度是解
目录
⚠️ 问题:进程之间的强依赖 —— 低模块性(Limited Modularity)
我们现在继续讲解 消息传递系统(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:用于传输“错误日志”
-
每个邮箱代表一条独立的通信链路,用途可以不同,策略也可以不同(如缓冲大小、优先级等)。
特性 | 描述 |
---|---|
建立条件 | 链接建立于共享邮箱的基础上,只有双方都知道该邮箱时才有效 |
多参与者 | 一个邮箱可以被多个发送者和接收者共享,支持多对多通信 |
多链接支持 | 同一对进程之间可以建立多个不同的通信链接(多个邮箱) |
在间接通信中,通信链接不是建立在进程对之间,而是建立在“共享邮箱”之上,从而支持多进程协作、多条通道、逻辑分离,大幅增强系统灵活性与可扩展性。
多个接收者共享一个邮箱时,消息由谁接收?
我们现在假设:
-
有三个进程:
P1
、P2
、P3
; -
它们都共享同一个邮箱
A
; -
P1
执行send(A, msg)
—— 向邮箱A
发送了一条消息; -
P2
和P3
同时在执行receive(A, msg)
—— 想从邮箱A
取出消息。
现在的问题是:谁会实际收到这条消息?是 P2
还是 P3
?
答案是:这取决于系统对“接收规则”的设计方式。
我们来逐一解释系统可能采用的三种策略:
1. 每个链接只允许两端通信(双端链接)
如果系统设计为:一个邮箱一次只能连接两个进程(例如:一个发送方 + 一个接收方),那就意味着:
-
一旦
P1
和P2
成为该邮箱的活跃通信对; -
P3
将无法从这个邮箱接收消息,除非切换链接。
❗ 这种方式限制灵活性,通常不常见,但在早期系统或特殊嵌入式场景下可能存在。
2. 一次只允许一个接收者使用 receive()
如果系统设计为:一个邮箱同一时刻只能有一个进程挂起在
receive()
上,
-
那么当
P2
和P3
都尝试执行receive(A, msg)
时,系统会只允许其中一个挂起等待; -
另一个会被拒绝或阻塞,直到前者完成。
这种方式可以避免竞争和冲突,但也会牺牲并发性。
3. 由系统来决定由谁接收消息(调度策略)
这是最常见也最灵活的一种方式:
系统允许多个接收者同时监听一个邮箱,然后:
🌀 系统自动决定谁来接收消息,有三种可能实现:
策略 | 说明 |
---|---|
任意选择(Arbitrary) | 系统随机选一个接收者(如 P2 或 P3),但不能同时发给两个 |
调度算法(如 Round-Robin) | 系统记录最近接收者,下一条消息轮换给下一个接收者 |
发件人控制(Sender-Aware) | 系统允许发件人知道哪个进程接收了消息(有时也能指定优先接收者) |
这种方式类似于“事件分发”机制,非常适合并发服务场景,比如:
-
一个邮箱代表一个任务队列;
-
多个 worker(P2, P3)监听邮箱;
-
谁空闲就谁来处理。
系统策略 | P1 发出消息后,P2 与 P3 的行为 |
---|---|
限定双端 | 只能一对通信,另一个进程无法接收 |
限定单接收者挂起 | 只有一个进程能监听,另一个等待 |
系统调度 | 任意、轮询或指定的方式决定哪个进程接收 |
当多个进程共享同一个邮箱时,谁来接收消息,取决于系统对通信链接策略、并发接收管理、邮箱所有权等方面的设计 —— 大多数现代系统采用“系统调度”的方式,在多个监听者中公平或按策略分配消息接收权。
更多推荐
所有评论(0)