图 1:本文封面,五种 IPC 方式的一句话特征对比。
先说明属性:这是一篇经典题解析,题目出自本地历史面经资料,与任何公司的当前招聘无关。「进程间通信有哪些方式」是操作系统部分的保留题,难不倒背书的人,但面试官想听的不是罗列名词,而是每种方式在速度、数据形态、同步语义上的差异,以及「这个场景为什么选它」。
管道:最简单,也最受限。
匿名管道由 pipe() 创建,给父子进程之间搭一条单向字节流通道:一头写、一头读,数据没有消息边界,读端空了就阻塞、写端满了也阻塞,同步语义是自带的。想要无关进程之间用,就换命名管道(FIFO),它在文件系统里有个名字,任何进程按权限打开即可。Linux 手册里还有一个常被追问的细节:不超过 PIPE_BUF 字节的写入保证原子性,超过则可能与其他写者的数据交错。管道的短板很明显:单向、字节流无格式、只能本机。
消息队列:带类型的消息,天然解耦。
消息队列由内核维护,收发双方按「消息」为单位交换数据,每条消息可以带类型字段,接收方可以按类型挑着收。相比管道,它保留了消息边界,而且发送方写完就走,不必等接收方在线——内核缓冲区兜着,天然支持异步解耦和削峰。代价是每次收发都要在用户态和内核态之间拷贝数据,大数据量时开销可观。
共享内存:最快的路,但锁要自己造。
共享内存把同一段物理内存映射进多个进程的地址空间,数据根本不经过内核拷贝,一方写入、另一方立刻可见,是五种方式里速度最快的,适合大数据量、高频交换的场景。但它只解决「数据怎么到」,不解决「什么时候读」——两个进程同时写就乱套,所以共享内存几乎总是和信号量成对出现。
信号量:不传数据,专管同步。
信号量本质是内核维护的计数器,P 操作减一(不足则阻塞)、V 操作加一,用来做互斥和条件同步。它不搬运任何业务数据,角色是给共享内存这类「裸通道」配上交通规则。把它和消息队列对比着记就不容易混:一个管同步,一个管数据。
Socket:唯一能跨机器的选项。
Socket 走网络协议栈,支持字节流(TCP)和数据报(UDP)两种形态,本机通信可以用 Unix domain socket 减少开销。它是五种方式里唯一能跨机器的,所以分布式系统的节点间通信没得选;但在同机场景下它的开销最大,协议栈封装、拷贝、上下文切换一样不少。
选型矩阵:先定场景,再选方式。
把五个维度摆在一起看:本机父子进程传小数据,管道最省事;要异步解耦、削峰填谷,消息队列;数据量大且交换频繁,共享内存配信号量;要跨机器,只有 Socket。面试里能主动把「数据形态」(字节流还是带边界消息)和「同步语义」(自带阻塞还是要自己加锁)这两个维度讲出来,就是明显超出背诵水平的信号。
用代码验证管道的字节流与阻塞语义。
下面这段 C 程序在 macOS 与 Linux 上都能编译,用 clang 实测(clang -o pipe_demo pipe_demo.c && ./pipe_demo):父进程往管道写一句话,子进程阻塞读出,写端关闭后再读返回 0(EOF)。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
int fd[2];
pipe(fd); /* fd[0] 读端,fd[1] 写端 */
pid_t pid = fork();
if (pid == 0) { /* 子进程:读 */
close(fd[1]);
char buf[64] = {0};
ssize_t n = read(fd[0], buf, sizeof(buf) - 1); /* 无数据时阻塞 */
printf("子进程读到 %zd 字节:%s\n", n, buf);
printf("再次 read 返回值 = %zd(写端关闭,读到 EOF)\n", read(fd[0], buf, 1));
close(fd[0]);
} else { /* 父进程:写 */
close(fd[0]);
const char *msg = "hello via pipe";
write(fd[1], msg, strlen(msg));
close(fd[1]); /* 关闭写端,子进程才能读到 EOF */
wait(NULL);
}
return 0;
}
实测输出:
子进程读到 14 字节:hello via pipe
再次 read 返回值 = 0(写端关闭,读到 EOF)
两次 read 的行为印证了管道的两条核心语义:读端无数据时阻塞等待,所有写端关闭后读到 EOF。
图 2:五种 IPC 方式在四个维度上的选型矩阵。
面试现场的答题结构。
先用一句话总起「五种方式没有银弹,选型看速度、数据形态、同步语义、是否跨机」;然后按管道、消息队列、共享内存、信号量、Socket 逐一讲机制与短板;重点展开共享内存为什么快(不经过内核拷贝)以及为什么必须配信号量;最后落到场景选型口诀收尾。你的项目里用哪种 IPC 最多?有没有踩过共享内存没配好同步的坑?欢迎讲讲。