进程通信的学习
共享内存(Shared Memory)—— 像共用一个黑板
初始化时需要内核介入,但后续通信可直接在用户态完成:
-
🧾 比喻:两个房间共享一个“黑板”,你写,我读,大家都能看到。
-
➕ 优点:传输速度快,因为不用来回搬数据(就在内存里)。
-
➖ 缺点:需要加锁,不然容易“写错乱”,像两个人同时擦黑板、写字,会打架。
✅ 使用场景:数据量大、频繁交流的进程之间通信,比如视频播放、数据处理等。
1. 基于数据结构的共享(结构化共享)
定义:进程之间通过预定义的数据结构(如消息格式、协议、类/对象等)交换数据,双方必须严格遵守这种结构才能正确解析数据。
通俗比喻:
假设两个人要交换包裹,他们必须提前约定好包裹的格式,比如:
-
包裹必须用红色箱子装;
-
第一层放书,第二层放衣服,第三层放零食;
-
箱子外必须贴标签标明内容。
双方严格按照这个格式打包和拆包,即使他们不面对面交接(比如通过快递),也能准确理解对方传递的信息。
技术场景:
-
消息队列(Message Queue):发送方和接收方约定消息的字段(如
{type: "order", data: "..."}
)。 -
RPC(远程过程调用):调用远程函数时,参数和返回值需要序列化成结构化的数据(如JSON、Protobuf)。
-
网络协议(HTTP、TCP包头):数据包必须包含协议规定的头部和字段。
特点:
-
强约束:数据必须按固定格式组织,灵活性低,但可靠性高。
-
跨语言/跨平台:适合不同语言或系统间的通信(比如Java程序向Python程序发JSON数据)。
-
需要编解码:数据可能需要序列化(编码)和反序列化(解码)。
2. 基于存储区的共享(原始内存共享)
定义:进程直接读写同一块物理内存区域,数据以原始二进制形式存在,没有预定义的结构,进程自行决定如何解释这些数据。
通俗比喻:
两个画家共用一块白板作画,白板本身没有分区或格式限制:
-
画家A在白板左边画了一只猫;
-
画家B看到后,直接在右边补了一条鱼;
-
两人需要协调作画时间(比如用信号量),否则可能同时修改同一区域,导致画面混乱。
技术场景:
-
共享内存(Shared Memory):多个进程访问同一块内存区域,直接读写二进制数据。
-
内存映射文件(Memory-Mapped File):文件内容映射到内存,进程直接操作内存来修改文件。
特点:
-
高效:无需数据复制或编解码,直接操作内存,速度极快。
-
无结构:数据是“原始”的,进程自行定义如何解析(比如前4字节是整数,后100字节是字符串)。
-
需同步:必须用锁、信号量等机制避免数据竞争(如两个进程同时修改同一地址)。
-
依赖系统:通常仅限同一台机器的进程使用。
消息队列(Message Queue)—— 像留言板或邮箱
-
📮 比喻:像在公共区域放一个“邮箱”,一个人投递信息,另一个人来读取。
-
➕ 优点:不需要亲戚关系,任何进程都可以通过这个邮箱沟通。
-
➖ 缺点:复杂度比管道略高,而且消息数量和大小有限制。
✅ 使用场景:多个程序之间解耦、协作,例如生产者-消费者模型。
消息队列(Message Queue)—— 快递柜
通信都需要用户态到内核态的切换,由内核直接管理通信过程:
📦 类比场景:
你和同事通过公司楼下的 公共快递柜 传递文件:
-
独立存储:每个文件被包装成独立包裹(消息),带类型标签。
-
异步处理:你放包裹后不用等同事,对方有空时凭类型标签取件。
-
持久性:快递柜断电后包裹还在(部分消息队列支持持久化)。
计算机中的消息队列:
-
内核维护的链表结构,进程通过
msgget()
、msgsnd()
、msgrcv()
操作。 -
适合多对多通信,且允许非实时处理(如日志收集)。
1. 直接通信方式(点名道姓)
定义:进程在通信时必须明确指定对方的标识(如进程ID、名称或地址),类似打电话需要知道对方的号码。
通俗比喻:
-
想象你和朋友约饭,直接发微信给他:“张三,今晚6点老地方见!”
-
必须明确对方是谁,且双方需“在线”(如进程必须存在且准备好通信)。
技术场景:
-
管道(Pipe):父进程和子进程通过管道直接通信。
-
Socket直连:客户端直接指定服务器IP和端口发送请求。
-
信号(Signal):进程A向进程B发送
SIGTERM
终止信号(需知道B的PID)。
特点:
-
强耦合:通信双方依赖彼此的身份(如进程ID),一方退出可能导致通信失败。
-
点对点:通常是一对一通信。
-
实时性:要求双方同时存在(类似打电话需要对方接听)。
2. 间接通信方式(匿名投递)
定义:进程通过中间媒介(如消息队列、共享内存或文件)通信,无需知道对方是谁,类似把信投到邮筒,邮局负责传递。
通俗比喻:
-
你在公司群里发消息:“今晚6点聚餐,想来的接龙!”
-
无需指定具体接收者,感兴趣的人自行查看消息。
-
即使你发消息时有人不在线,他们之后也能看到(异步通信)。
技术场景:
-
消息队列(Message Queue):进程A向队列发送消息,进程B从队列读取。
-
共享内存(Shared Memory):多个进程读写同一块内存区域。
-
文件:进程A写入文件,进程B稍后读取。
-
发布-订阅模型:进程向主题(Topic)发布消息,订阅该主题的进程接收。
特点:
-
解耦:发送方和接收方无需知道彼此存在(通过中间媒介隔离)。
-
灵活性:支持一对多、多对多通信(如多个生产者向队列发消息,多个消费者读取)。
-
异步性:接收方可以延迟处理消息(如消息队列堆积)。
共享存储和间接存储的区别
🌟 一、共享存储(Shared Storage)
✅ 通俗理解:
就像你和朋友一起用一个笔记本(共享内存),你写一段话,他马上能看到,速度快、效率高。
📌 特点:
-
数据在共享内存区中,多个进程可读可写
-
速度快(不需要数据复制)
-
需要自己处理同步问题(比如防止你们两个同时写进而“打架”)
📚 举例:
-
共享内存(Shared Memory)
-
内存映射文件(mmap)
🌟 二、间接存储(Indirect Storage)
✅ 通俗理解:
像你写了纸条交给老师(操作系统),老师再交给你的同学。你们之间不直接接触数据,而是通过“中间媒介”。
📌 特点:
-
通信通过系统提供的中间机制完成(比如消息队列)
-
进程之间看不到彼此的内存
-
安全性好,但速度比共享慢一些
📚 举例:
-
消息队列(Message Queue)
-
管道(Pipe)
-
套接字(Socket)
管道(Pipe)—— 单向水管
🚰 类比场景:
想象你和朋友用一根 只能单向流水 的水管传递纸条:
-
单向性:纸条只能从你这一端塞进去,朋友从另一端取出。
-
血缘限制:这根水管只能在你和亲兄弟姐妹(父子进程)之间用。
-
容量限制:水管一次只能传少量纸条,满了会堵住,空了会等待。
计算机中的管道:
-
本质是内核中的一段缓冲区,通过
pipe()
系统调用创建。 -
适合简单场景,比如在命令行中用
ls | grep "txt"
将ls
的结果传给grep
。
管道通信(Pipe)—— 像传纸条
-
📝 比喻:两个房间之间开了一个“投纸条的通道”,一个人写纸条塞进去,另一个人接着看。
-
➕ 优点:简单,适合父子进程(比如一个程序启动另一个程序)。
-
➖ 缺点:只能一个方向传纸条(单向管道),而且只能用于有亲戚关系的进程。
✅ 使用场景:父进程和子进程传输数据,比如 ls | grep
这样的命令管道。
管道通信和共享存储的区别
一句话总结:
-
管道通信:像“传纸条”,一人写、一人读,有顺序、速度适中。
-
共享存储:像“共用一个黑板”,大家可以自由写写画画,速度快但容易冲突。
🌟 一、管道通信(Pipe)
✅ 通俗理解:
想象你和朋友在隔壁房间,中间有个小洞,你写了张纸条塞过去,他收到并阅读。这就是“管道”——
-
一边写,一边读
-
数据一旦读了,就没了(像传过去的纸条看完就丢了)
📌 特点:
-
单向(或双向)传递数据
-
读写有顺序,像排队
-
数据传完就清空(不可回头看)
-
一般用于父子进程之间的通信
🌟 二、共享存储(Shared Memory)
✅ 通俗理解:
你和朋友共用一块黑板,你写在左边,他读右边,中间没人挡。大家都能随时访问黑板上的内容。
-
数据就在那里,随时写,随时看
-
你写的内容不会消失(除非擦掉)
📌 特点:
-
速度快(因为直接访问内存)
-
可以大容量传数据
-
但要小心写冲突,需要“加锁”来避免打架