进程间通信(IPC,Inter-Process Communication)是后端面试的高频考点。因为进程内存隔离,要交换数据就得走专门的机制。这篇文章把四种核心 IPC 方式一次讲清楚。
管道(Pipe)
最简单的 IPC,本质是内核维护的一块缓冲区,单向流动(半双工)。
三个关键概念:
内核维护: 管道这块内存不是程序自己分配的,是调用 pipe() 系统调用后,操作系统在内核空间里开辟的。程序没法直接访问,只能通过 read()/write() 系统调用来读写。
缓冲区: 管道不是实时传输,有一块中间存储区。写进去的数据先放在这里,读端什么时候来取都行。这块缓冲区有大小限制(Linux 默认 64KB),写满了写端会阻塞。
单向流动: 数据只能从写端流向读端,不能反向。创建管道时得到两个文件描述符:fd[0](读端)和 fd[1](写端)。双向通信需要创建两个管道。
分类:
- 匿名管道: 只能用于有亲缘关系的进程(父子进程)
- 命名管道(FIFO): 有文件路径,任意进程都能打开
适合场景: 简单的父子进程通信、流式数据传输。shell 里的 | 就是匿名管道。
消息队列(Message Queue)
内核维护的消息链表,进程可以往里放消息、取消息。
和管道的核心区别:
| 管道 | 消息队列 | |
|---|---|---|
| 消息边界 | 无(字节流,可能粘包) | 有(每条消息独立) |
| 读取方式 | 只能顺序读 | 可按消息类型选择性读取 |
| 生命周期 | 依附于进程 | 内核对象,进程退出后仍存在 |
安全隐患: 消息队列是内核级共享资源,如果创建时权限设得太宽松(如 0666),同机器上任意进程都能读写。防范方式:收紧权限(0600)、消息体加签名验证、或换用 Unix Domain Socket。
适合场景: 需要消息分类、异步解耦、进程重启后继续处理的场景。
共享内存(Shared Memory)
最快的 IPC 方式。两个进程把同一块物理内存映射到各自的虚拟地址空间,直接读写,不需要数据拷贝。
- 速度最快(零拷贝,不经过内核)
- 本身没有同步机制,需要配合信号量或互斥锁防止并发冲突
- 适合大数据量传输
类比: 两个工人共用一块白板,谁都能写,但要约定好谁在写的时候另一个不能动。
信号量(Semaphore)
严格来说不是用来传数据的,而是用来做同步和互斥的。
本质是一个计数器,支持两个原子操作:
- P 操作(wait): 计数器 -1,如果 < 0 就阻塞
- V 操作(signal): 计数器 +1,如果有等待的进程就唤醒
二值信号量(值只有 0/1) 就是互斥锁,用来保护临界区。计数信号量 用来控制并发数量。
常见搭配: 共享内存 + 信号量,共享内存负责传数据,信号量负责协调访问顺序。
其他 IPC 方式
| 方式 | 特点 |
|---|---|
| 信号(Signal) | 异步通知,只能传信号编号,不能传数据(如 kill -9) |
| Socket | 可跨机器,本地也能用(Unix Domain Socket),最通用 |
| 内存映射文件(mmap) | 类似共享内存,但基于文件,进程重启数据不丢 |
面试答题思路
问”进程间通信有哪些方式”,答完列举后,面试官通常会追问:
- 管道和消息队列的区别? → 有无消息边界、能否随机读取、生命周期
- 共享内存为什么最快? → 零拷贝,直接操作内存,不经过内核
- 信号量和互斥锁的区别? → 信号量可以跨进程,互斥锁通常在同一进程内的线程间用
- 消息队列有安全问题吗? → 有,权限控制不严格时其他进程可以读写,需要收紧权限或加消息认证
理解 IPC 的本质,再去看分布式系统里的消息队列(Kafka、RocketMQ)、共享缓存(Redis)等,会发现思路是一脉相承的——都是在解决”不同执行单元之间如何安全高效地交换数据”这个问题。