进程间通信方式总结
进程之间默认是相互隔离的,彼此看不到对方的用户空间。所以如果多个进程想交换数据、协作执行、同步先后顺序,就必须借助操作系统提供的进程间通信机制,也就是 IPC(Inter-Process Communication)。
理解 IPC 时,最重要的一句话是:
因为进程间用户空间默认隔离,所以通信通常要借助内核空间,或者借助操作系统建立的共享映射。
这篇就把常见 IPC 方式系统整理一遍,并补一些容易混的点。
一、为什么需要 IPC
线程之间因为共享同一进程的地址空间,所以通过共享变量就能直接通信。进程不一样,进程 A 不能直接访问进程 B 的用户空间,因此必须通过操作系统提供的通信机制来实现:
- 交换数据
- 发送通知
- 保证执行顺序
- 协调共享资源访问
所以 IPC 不只是“传数据”,还包括:
- 数据交换
- 同步
- 互斥
- 异步通知
二、管道(Pipe)
管道是最基础的一类 IPC。
1. 匿名管道
最常见的例子就是 shell 里的:
ps auxf | grep mysql这里的 | 背后就是匿名管道。
匿名管道本质上是:
内核里的一段缓冲区,一端写入,另一端读取。
特点
- 通常是单向通信
- 数据是无格式字节流
- 一般适合有亲缘关系的进程,最典型是父子进程
- 生命周期较短,随着相关进程结束而失去意义
为什么适合父子进程
因为父进程在 fork 前创建管道后,子进程会继承对应的文件描述符,这样父子就都能访问同一个管道对象。
双向通信怎么办
一个普通管道最典型的用法是半双工,也就是一边写一边读。如果要实现双向通信,通常需要:
创建两个管道
- 管道 1:父写子读
- 管道 2:子写父读
2. 命名管道(FIFO)
命名管道和匿名管道的主要区别是:
它在文件系统里有一个路径名。
可以通过 mkfifo 创建,例如:
mkfifo myPipe特点
- 不要求父子关系
- 不相关进程只要都知道这个路径,就能通信
- 本质上仍然是内核中的管道缓冲区,不是普通数据文件
- 仍然是字节流、通常按 FIFO 语义工作
3. 管道的优缺点
优点
- 简单
- 容易理解
- shell 场景非常方便
缺点
- 匿名管道通常局限在父子进程
- 通常是单向 / 半双工使用方式
- 没有消息边界
- 缓冲区大小有限
- 不适合高频大量数据交换
三、消息队列(Message Queue)
消息队列可以理解成:
内核维护的一种“按消息为单位”的队列结构。
和管道最大的区别在于,管道是无格式字节流,而消息队列是:
一条一条消息地传输
特点
- 不要求父子关系
- 按消息为单位通信
- 发送方和接收方可以异步解耦
- 发送方写入后,通常不需要等待接收方立刻读取
为什么比管道更结构化
因为它有消息边界。
- 管道:一串字节
- 消息队列:一条消息、一条消息
所以消息队列更适合:
- 命令消息
- 任务消息
- 一来一回的结构化通信
缺点
- 单条消息长度有限
- 队列总容量有限
- 仍然存在数据拷贝开销
典型路径是:
发送进程用户空间
↓ copy
内核消息队列
↓ copy
接收进程用户空间所以消息队列虽然比管道更结构化,但并不以“高性能大数据传输”见长。
四、共享内存(Shared Memory)
共享内存是 IPC 里非常重要的一种,也是速度最快的一类 IPC 方式之一。
它的核心思想是:
让多个进程把各自的虚拟地址映射到同一块物理内存上。
这样一来:
- 进程 A 写进去
- 进程 B 可以直接看到
不需要像管道、消息队列那样在用户态和内核态之间来回复制数据。
为什么它快
因为它主要减少了:
- 用户空间 → 内核空间
- 内核空间 → 用户空间
之间的数据拷贝次数。
注意,这并不等于完全没有内核参与。共享内存建立映射时,内核仍然会参与;只是建立好之后,访问共享区本身不需要每次都走“中转复制”这条路。
为什么它麻烦
因为多个进程可能会同时操作同一块共享内存,这会带来:
- 数据覆盖
- 竞态条件
- 一致性问题
所以共享内存几乎总是要配合同步机制一起用。
五、信号量(Semaphore)
信号量本质上不是“传数据”的,而是:
用来做同步和互斥的。
它本质上是一个整型计数器,通过两个原子操作控制:
- P 操作:申请资源,计数器减 1
- V 操作:释放资源,计数器加 1
信号量能做什么
1. 互斥
如果信号量初始化为 1,就表示:
同一时刻只允许一个执行流进入临界区
这就是互斥。
2. 同步
如果信号量表示“某个条件是否满足”,那也可以用来约束顺序,比如:
- A 先做
- B 后做
这就是同步。
和共享内存的关系
你可以直接记:
- 共享内存:共享数据
- 信号量:保护共享数据、控制访问顺序
一个容易混的点
信号量不是消息队列,它不负责传输业务数据。它只负责:
- 资源数量控制
- 临界区互斥
- 执行顺序同步
六、信号(Signal)
信号和信号量名字很像,但完全不是一回事。
信号本质上是一种:
异步通知机制
它的作用不是“保护共享资源”,而是“告诉进程某件事发生了”。
常见例子
Ctrl + C→SIGINTkill -9 pid→SIGKILL- 子进程退出 →
SIGCHLD - 非法访问内存 →
SIGSEGV
特点
- 可以在任何时刻发给进程
- 更像一种事件通知
- 不适合传复杂业务数据
进程收到信号后通常怎么处理
有三种方式:
- 执行默认动作
- 捕捉信号并执行自定义处理函数
- 忽略信号
但有些信号不能被忽略或捕捉,比如:
SIGKILLSIGSTOP
最重要的区分
- 信号量:同步 / 互斥工具
- 信号:异步通知机制
七、Socket
Socket 是最通用的一类进程通信方式。
它既可以用于:
- 同一台机器上的进程间通信
- 也可以用于不同主机之间的网络通信
所以它比前面那些方式更通用。
1. TCP Socket
特点:
- 面向连接
- 字节流
- 可靠传输
常见流程:
服务端
socket()bind()listen()accept()
客户端
socket()connect()
建立连接后双方就能通过:
read/write- 或
send/recv
来通信。
2. UDP Socket
特点:
- 无连接
- 数据报
- 更轻量
通常不需要:
listen()accept()
而是更多使用:
sendto()recvfrom()
3. 本地 Socket(Unix Domain Socket)
本地 Socket 也是 Socket,但不走网络。
和 TCP/UDP 最大的区别在于:
它不是绑定 IP + 端口,而是绑定一个本地文件路径。
所以它特别适合同机进程通信,而且通常比本机 TCP 回环通信效率更高。
Socket 为什么通用
因为它能同时覆盖:
- 本地 IPC
- 客户端 / 服务端通信
- 跨主机网络通信
所以现实工程里,Socket 是最常见、最通用的进程通信方式之一。
八、这些 IPC 方式怎么选
你可以这样记:
管道
适合:
- 简单流式通信
- 父子进程
- shell 场景
消息队列
适合:
- 结构化消息
- 异步解耦
- 一条一条传消息
共享内存
适合:
- 高频、大量数据交换
- 对性能要求高的场景
但一定要配合同步机制。
信号量
适合:
- 做互斥
- 做同步
信号
适合:
- 异步通知
- 中断 / 控制行为
Socket
适合:
- 本机 IPC
- 网络通信
- 通用服务端 / 客户端通信
九、线程通信和进程通信有什么不同
这一点也值得补充。
进程通信
因为进程之间用户空间隔离,所以需要 IPC 机制。
线程通信
因为同一进程内线程共享地址空间,所以线程之间“通信”本身不难,直接共享变量就行。
线程更大的问题不在“怎么通信”,而在:
怎么避免并发访问冲突
所以线程更多关注的是:
- 锁
- 信号量
- 条件变量
- 原子操作
而不是 IPC 本身。
十、总结
由于进程之间的用户空间默认相互隔离,不能直接访问彼此的数据,所以操作系统提供了多种 IPC 机制。管道适合简单流式通信,尤其是父子进程;消息队列按消息组织,适合异步结构化通信;共享内存速度最快,但必须配合同步机制;信号量负责同步和互斥,不负责传数据;信号负责异步通知;Socket 最通用,既能做本地 IPC,也能做跨主机网络通信。理解这些机制时,关键不是死记名字,而是看它们分别在解决“数据怎么传”“顺序怎么控”“事件怎么通知”中的哪一类问题。
