进程间通信
进程的地址空间相互隔离,这是安全的代价——想交换数据,就必须借助操作系统提供的通道,这就是进程间通信(IPC)。线程因共享内存而不需要 IPC,多进程协作则全靠它。
为什么需要 IPC
典型场景:浏览器主进程与渲染进程分工;生产者把数据交给消费者;多个服务进程汇总统计结果。不同 IPC 在"速度、是否跨机器、能否携带结构化数据"上差异巨大,选错代价不小。
管道(Pipe)
管道是最古老的 IPC:数据像水流一样单向流过内核缓冲区,一端写、一端读。匿名管道只能用于有亲缘关系的进程(父子进程),典型应用就是 shell 命令拼接:
# 把前一个命令的输出作为后一个命令的输入
cat access.log | grep "ERROR" | wc -l
# 输出:42
无关进程之间用命名管道(FIFO):通过文件系统中的一个名字建立联系,先创建,再各自以读写方式打开使用。
消息队列与共享内存
消息队列:内核维护一个消息链表,进程按类型"投递/取走"消息,读走即删除、自带边界。相比管道,双方不必严格同步读写节奏。
共享内存:把同一块物理内存映射到多个进程的地址空间,读写直接命中内存,是速度最快的 IPC;缺点是需要自己解决互斥与同步。
// 片段:需包含头文件并处理返回值后才能运行
int shmid = shmget(KEY, 1024, IPC_CREAT); // 申请 1024 字节共享内存
char *ptr = shmat(shmid, NULL, 0); // 映射到本进程地址空间
strcpy(ptr, "hello"); // 直接写入
shmdt(ptr); // 解除映射
// 输出:无;另一进程 shmat 同一 KEY 后即可读到 "hello"
两者常配合使用:共享内存负责"高速搬数据",信号量负责"不让多个进程同时写"。
信号、信号量与 Socket
- 信号(signal):异步通知机制,不携带业务数据。如 Ctrl+C 给进程发 SIGINT、
kill -9发 SIGKILL。 - 信号量(semaphore):本身不传数据,只是一个计数器,用于互斥与同步(如限制同时访问某个资源的进程数)。
- Socket:既能本地通信(Unix Domain Socket),也能跨主机通信(TCP/UDP)——它是唯一能"跨机器"的 IPC,网络通信本质上也是一种 IPC。
IPC 对比速查
| 方式 | 速度 | 跨机器 | 数据形态 | 典型场景 |
|---|---|---|---|---|
| 管道 | 慢 | 否 | 字节流 | shell 命令拼接 |
| 消息队列 | 中 | 否 | 结构化消息 | 解耦、异步 |
| 共享内存 | 最快 | 否 | 任意数据 | 高性能数据交换 |
| 信号 | 快 | 否 | 几乎无 | 事件通知 |
| Socket | 中 | 是 | 字节流 | 网络服务 |
小结:选 IPC 的口诀是——"要快用共享内存,要解耦用消息队列,要通知用信号,要跨机器用 Socket"。再补一句"共享内存必须配合信号量或锁使用",这条知识点就闭环了。