进程间通信方式有哪些?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
-
知识面广度:面试官不想听你背名词,想听的是你能不能系统地说出主流 IPC 方式,讲清楚各自的适用场景。
-
原理理解深度:管道为什么要走内核缓冲区?共享内存为什么是最快的 IPC?这些 “为什么” 才是拉开差距的地方。
-
联系实际的意识:这道题答完,面试官大概率会顺势追问 “Java 里进程间怎么通信”,考察你能不能把操作系统知识和实际开发串起来。
核心答案
先说结论:Linux 中的进程间通信方式主要有 7 种:
| 通信方式 | 数据流向 | 关键特性 | 适用场景 |
|---|---|---|---|
匿名管道 pipe |
半双工 | 依赖亲缘关系(父子进程) | 父子进程间简单字节流通信 |
命名管道 FIFO |
半双工或双工(开两个) | 文件系统中有名字,无亲缘限制 | 任意本机进程间通信 |
| 消息队列 | 双向可建 | 消息为单位,有类型、有格式 | 异步、解耦的少量数据传输 |
| 共享内存 | 直接读写 | 最快的 IPC,需配合同步机制 | 大数据量、高性能传输 |
| 信号量 | 不传数据 | 同步互斥原语 | 配合共享内存做保护 |
信号 signal |
单向异步 | 唯一的异步机制,通知事件为主 | 进程杀死(kill -9)、异常通知 |
| Socket | 双向 | 唯一可跨主机的 IPC | 网络通信、本机跨进程(Unix 域套接字) |
一句话兜底:前四种传数据,信号量管同步,信号发通知,Socket 跨网络。
深度解析
一、管道:最古老但也最常用
管道分两种:匿名管道和命名管道。
匿名管道就是 shell 里那个 | 符号,比如 cat log.txt | grep "ERROR"。它的特点是:
- 半双工:数据只能单向流动,想双向通信就得开两根管道
- 亲缘限制:只存在于父子进程或兄弟进程之间,因为管道的文件描述符需要通过
fork继承传递 - 字节流:没有消息边界,读出来的是一连串字节,需要自己解析
命名管道(FIFO)就是来解决亲缘限制的:它在文件系统里有个名字,任何进程都能通过路径打开它,比如 /tmp/myfifo。
管道通信的本质是什么?看这张图:
图里的关键在于那个内核缓冲区:
- 进程 A 调用
write(),数据从 A 的用户空间拷贝进内核缓冲区 - 进程 B 调用
read(),数据再从内核缓冲区拷贝到 B 的用户空间 - 两边各不直接接触对方的内存,内核做中转
记住了 “两次拷贝” 这个细节,后面理解共享内存为什么快就顺理成章了。
另外管道还有个容量限制:Linux 下管道缓冲区默认是 64KB(16 页 × 4KB,Linux 2.6.11 起),写满了 write 会阻塞,读空了 read 会阻塞,所有 reader 都关闭时写端会收到 SIGPIPE 信号。想调整的话用 fcntl(fd, F_SETPIPE_SZ),不过非特权进程能调的上限受 /proc/sys/fs/pipe-max-size 约束,默认 1MB。
二、消息队列:有格式的管道
消息队列可以理解为 “升级版管道”,它是内核中一条消息的链表,克服了管道的几个短板:
- 有消息边界:管道是字节流,消息队列以 “一条消息” 为单位,写入什么格式读出来还是什么格式
- 有类型过滤:
msgrcv可以按消息类型type选择性接收,不用按顺序死等 - 异步解耦:A 进程发完消息就可以走人,B 进程想什么时候读就什么时候读,不要求两边同时在线
消息队列本身也有两种体系,面试提到能加分:
- System V 消息队列:
msgget/msgsnd/msgrcv这一套,比较老 - POSIX 消息队列:
mq_open这一套,API 更现代,支持消息优先级
不过消息队列也有硬伤:每次收发消息都要经过内核,数据要在用户态和内核态之间来回拷贝,而且单条消息有大小上限(System V 下默认 8192 字节,/proc/sys/kernel/msgmax 可查),大块数据传输就别指望它了。
三、共享内存:性能之王
重点来了。面试官问到 IPC,共享内存是必考点,尤其是 “为什么它最快” 这个追问。
原理:两个进程的虚拟地址空间,映射到同一块物理内存。A 写进去的东西,B 立刻就能看到,全程不经过内核中转。
- 进程 A 和进程 B 各自的虚拟地址可以不一样,但通过各自的页表,最终指向同一块物理内存
- 写入方一旦写入,读取方立即可见,没有中间商赚差价
- 对比管道的 “用户态 → 内核 → 用户态” 两次拷贝,共享内存一次拷贝都不需要(建立映射后)
当然,天下没有免费的午餐。共享内存不提供任何同步机制,两个进程同时读写同一块内存就是数据竞争,所以它几乎总是和信号量搭配出现:信号量管 “谁能读写、什么时候能读写”,共享内存管 “数据放哪”。
共享内存相关的经典 API 就是这一套:shmget 创建、shmat 挂接(attach)、shmdt 脱离、shmctl 控制。
四、信号量:不传数据的 IPC
严格说,信号量本身不传输数据,它是一个同步互斥原语,核心就是那个 P/V 操作:
- P 操作(wait):信号量值减 1,减完小于 0 就挂起等待
- V 操作(signal):信号量值加 1,有等待者就唤醒它
信号量的值大于 0 时表示可用资源数,等于 0 时表示资源耗尽。它管的是 “谁先谁后、谁进谁等” 这件事。面试时把信号量归类说清楚——“它解决的是进程间的同步与互斥,而不是数据传输”,这句话能让面试官知道你概念是清晰的。
五、信号:唯一的异步机制
信号是所有 IPC 方式中唯一的异步通信方式。进程不需要阻塞等待,信号随时可能到来,内核会中断进程当前的执行流,转去执行信号处理函数。
日常开发里你天天在用它,只是可能没意识到:
kill -9 <pid>就是给进程发SIGKILL信号Ctrl + C是给前台进程发SIGINTkill -15 <pid>是发SIGTERM,进程可以捕获它做优雅退出——这也是为啥停服务建议用-15而不是-9:-9无法被捕获,进程没有任何清理资源的机会
注意 SIGKILL(9)和 SIGSTOP(19)这两个信号不能被捕获、阻塞或忽略,属于内核的 “保底手段”。
六、Socket:唯一能跨主机的 IPC
前面所有方式都只能在本机内通信,只有 Socket 突破了这个限制——它不仅能跨进程,还能跨主机,网络通信靠的就是它。
很多人没注意到,Socket 还有个本机玩法:Unix 域套接字(Unix Domain Socket)。它不走网络协议栈,专门用于同一台主机上的进程通信,比走 TCP 回环接口快不少。像 MySQL、Docker 的守护进程监听的就是 /var/run/docker.sock 这种 Unix 域套接字。
对 Java 开发者来说,Socket 就更熟悉了。进程间通信在 Java 生态里的对应物:
- 跨进程:
Socket/ServerSocket、HTTP 接口调用、RMI - 跨系统解耦:消息中间件(RocketMQ、Kafka),可以理解为分布式版的 “消息队列”
- 本机协调:文件锁、
RandomAccessFile+ 文件通道
给一个最简单的 Java Socket 双向通信骨架:
// 服务端进程:监听 8080 端口
try (ServerSocket serverSocket = new ServerSocket(8080)) {
Socket client = serverSocket.accept(); // 阻塞等待连接
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
System.out.println("收到客户端消息: " + in.readLine());
}
// 客户端进程:连接服务端并发送一条消息
try (Socket socket = new Socket("127.0.0.1", 8080)) {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("hello from another process");
}
这就是两个独立 JVM 进程之间用 Socket 通信的最小实现,本质上和 C 里调用 socket API 是一回事,Java 只是把系统调用包装成了面向对象的接口。
七、一张表看懂怎么选
| 你的需求 | 推荐方式 |
|---|---|
| 父子进程间传简单数据 | 匿名管道 |
| 本机任意进程间传数据 | 命名管道 / Unix 域套接字 |
| 大数据量、高频通信、追求性能 | 共享内存 + 信号量 |
| 异步解耦、不要求实时 | 消息队列 |
| 只需要通知 “某事件发生了” | 信号 |
| 跨主机分布式通信 | Socket |
面试高频追问
-
为什么共享内存是最快的 IPC 方式?
因为数据不需要在内核和用户态之间来回拷贝。管道、消息队列都要经历 “发送方用户态 → 内核 → 接收方用户态” 的两次拷贝,而共享内存建立映射后,双方直接读写同一块物理内存,零拷贝。
-
进程间通信和线程间通信有什么区别?
进程地址空间隔离,通信必须借助内核提供的机制;线程共享同一进程的地址空间,全局变量天然可见,通信的核心问题反而是 “同步互斥”(锁、
wait/notify)。一个重在 “怎么传数据”,一个重在 “怎么防打架”。 -
共享内存有了,为什么还需要消息队列?
共享内存快但不管同步、不管消息边界,需要自己实现协议;消息队列自带边界和类型,用起来简单安全。性能不是唯一标准,开发效率和正确性同样重要。
-
Java 进程之间怎么通信?
Socket / HTTP / RMI 直连,或者用 RocketMQ、Kafka 这类消息中间件做异步解耦。注意 Java 没有直接暴露共享内存 API,需要通过 JNI 或者依赖一些映射文件的方案(
MappedByteBuffer操作内存映射文件算是个近似玩法)。
常见面试变体
- “管道和消息队列有什么区别?”
- “共享内存为什么快?它有什么缺点?”
- “信号和信号量的区别?”(这俩名字像,完全是两个东西:signal 是异步通知,semaphore 是同步计数器)
- “介绍一下零拷贝?”(从共享内存和
sendfile、mmap的角度都能切入,属于这块的高阶延伸)
记忆口诀
七种 IPC:管道匿名靠父子,命名管道随便聊;消息队列有格式,共享内存速度高;信号量来管同步,信号异步把事报;Socket 唯一跨主机,本机还有域套接字。
总结
一句话:IPC 七种方式,管道传流、队列传消息、共享内存拼性能、信号量管同步、信号发通知、Socket 跨网络。把 “共享内存为什么快(免拷贝)” 和 “信号量只是同步工具不传数据” 这两个点说透,再顺势聊到 Java 里的 Socket 和 MQ,这道题就算答得漂亮了。
