进程间通信方式有哪些?
进程有独立地址空间,彼此看不见对方的内存,所以通信必须借助内核中转或共享映射。 常见六类:管道(pipe/FIFO)、消息队列、共享内存、信号量、信号、套接字(socket,含 Unix domain)。 选型只看三点:数据量、是否需要双向/跨机、能不能接受拷贝。
1. 六种方式对比
| 方式 | 是否内核中转 | 数据拷贝 | 方向 | 跨主机 | 适用场景 |
|---|---|---|---|---|---|
| 匿名管道 pipe | 是 | 2 次 | 单向 | 否 | 父子进程、shell 管道 |
| 命名管道 FIFO | 是 | 2 次 | 单向 | 否 | 无亲缘关系进程 |
| 消息队列 msgqueue | 是 | 2 次 | 双向 | 否 | 有格式的小消息 |
| 共享内存 shm | 否 | 0 次 | 双向 | 否 | 大数据量、高频 |
| 信号量 semaphore | 是 | 无数据 | — | 否 | 只做同步,不传数据 |
| 信号 signal | 是 | 无数据 | 单向 | 否 | 事件通知(kill) |
| Unix socket | 是 | 2 次 | 双向 | 否 | 本机 C/S,可靠有序 |
| TCP socket | 是 | 多次 | 双向 | 是 | 跨机通信 |
进程 A 内核 进程 B
write() ──► [管道缓冲区 64KB] ──► read()
(两次拷贝:A→内核,内核→B)
进程 A ──┐
├─► [同一块物理页] ◄── 需要信号量/锁保护
进程 B ──┘ (零拷贝,最快)
2. 共享内存为什么最快,又为什么最难用
最快是因为它完全绕过内核:两个进程的页表指向同一批物理页,写方一落笔,读方立刻可见,没有系统调用、没有拷贝。
最难用是因为它不提供任何同步:读方可能在写方写到一半时读到脏数据,所以必须配信号量、互斥锁或原子变量。 而且共享内存段要显式创建、映射、解除、删除,生命周期管理容易漏。
// Unix domain socket:本机最常用的"进程间 TCP"
ln, _ := net.Listen("unix", "/tmp/demo.sock")
defer os.Remove("/tmp/demo.sock")
go func() {
for {
conn, err := ln.Accept()
if err != nil { return }
go func(c net.Conn) {
defer c.Close()
io.Copy(c, c) // echo
}(conn)
}
}()
// 共享内存:mmap 一块匿名共享区,父子进程都能看到
b, err := syscall.Mmap(-1, 0, 4096,
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED|syscall.MAP_ANON)
if err != nil { log.Fatal(err) }
copy(b, []byte("hello from parent"))
// fork 出的子进程继承该映射,读到同样的内容;写回时用原子操作或信号量同步
3. 怎么选
# 看系统里现有的 IPC 资源
ipcs -q # 消息队列
ipcs -m # 共享内存段
ipcs -s # 信号量
ls -l /proc/1234/fd | grep pipe # 进程持有的管道
# 造一个命名管道试试
mkfifo /tmp/f && echo hi > /tmp/f &
cat /tmp/f
经验规则:
- shell 里串命令 → 匿名管道;
- 本机两个服务互调,要可靠有序 → Unix domain socket(还能传文件描述符);
- 高频、大数据量、双方都是自己写的程序 → 共享内存 + 信号量;
- 只是通知"该干活了" → 信号或 eventfd;
- 跨机器 → 只能 socket(TCP/UDP)。
4. 常见追问
Q:管道为什么是单向的,容量多大?
管道是内核里一个环形缓冲区,读写两端各持一个 fd,设计上就是单向流;双向要开两条。
Linux 默认容量 64KB(/proc/sys/fs/pipe-max-size 可调上限),写满后 write 会阻塞,这就是"背压"。
Q:匿名管道和命名管道差在哪?
匿名管道只能靠 fork 继承 fd 来共享,所以只能用于有亲缘关系的进程;FIFO 在文件系统里有名字,
任何进程 open 都能接入,但数据仍在内核缓冲区,不落盘。
Q:Unix socket 比 TCP 快吗?
本机上通常更快:不经过网络协议栈的校验和、路由、拥塞控制,直接走 socket 层;
还支持 SCM_RIGHTS 传递文件描述符和凭证。但它不能跨机。
Q:信号能传数据吗?
几乎不能,标准信号只带一个编号,sigqueue 可以附带一个 int 或指针值。信号适合"通知",
不适合"传输",而且处理函数里只能调异步信号安全的函数。
小结
通信方式的选择是"拷贝次数 × 同步复杂度 × 使用范围"的权衡:管道简单但单向且要拷两次, 共享内存零拷贝但要自己做同步,socket 通用且能跨机。答这题给出对比表 + 一句"共享内存必须配信号量", 再补 Unix socket 传 fd 这个细节,就能拉开差距。