进程间通信
复习
- 多级反馈队列:不知道任务长短时,靠反馈自动调整优先级
- 多核调度:任务分到多个 CPU 后还要处理负载和缓存亲和
- 线程与并发:一个进程内可以有多条共享内存的执行线
TL;DR
- 进程之间内存隔离,互相看不到对方的数据
- 要交换数据,得借助内核提供的专门通道
- 常见方式:管道、消息、信号、共享内存
- 各有取舍:共享内存最快,但要自己处理同步
正文
上一章说,同一进程里的线程共享内存,交换数据很方便。可进程与进程之间是隔离的——这也是它们的优点:一个进程出错,不会直接改坏另一个进程的数据。
但隔离也带来麻烦:两个进程想交换数据,怎么办?
为什么不能直接读写对方内存
每个进程都有自己独立的地址空间(后面会专门讲)。进程 A 眼里的地址 1000,和进程 B 眼里的地址 1000,很可能对应完全不同的物理内存。A 也没法凭一个地址,去访问 B 的数据。
所以,进程间通信(IPC,Inter-Process Communication)必须借助内核:由内核提供一条双方都能用的通道,数据经由这条通道传递。
几种常见方式
内核提供的通道有好几种,各有各的脾气:
- 管道(pipe):一条单向的字节流,像一根水管。一端往里写,另一端从里读。适合把“前一个程序的输出”接到“后一个程序的输入”。
- 消息队列:一条条有边界的“消息”。每发一条,对方就收到一条,能保留消息之间的分界。
- 信号(signal):一个简短的通知,用来“打断”对方,比如告诉它“该结束了”。它只传递很少的信息。
- 共享内存:由内核划出一块内存,让两个进程都能访问。数据不走内核中转,速度最快;但正因为大家都能直接读写,必须另外配一套同步机制,否则会互相踩脚。
前三种都要经过内核搬运或转发;共享内存则让双方直接读写同一块地方,省去了搬运。
怎么选
| 方式 | 传递什么 | 速度 | 同步难度 |
|---|---|---|---|
| 管道 | 字节流 | 一般 | 低 |
| 消息队列 | 有边界的消息 | 一般 | 低 |
| 信号 | 简短通知 | 快 | 低 |
| 共享内存 | 任意数据 | 最快 | 高 |
一句话:想省事就用管道或消息,想最快就用共享内存,但要额外操心同步。 怎么在这两者之间取平衡,正是接下来几章要展开的。
而“共享内存要同步”这件事,其实不只发生在进程之间——同一进程里的多个线程共享内存,也会撞上同样的麻烦。下一章,我们从最简单的一个例子看起:两个线程同时给一个数加一,为什么会加错?
思考题
既然共享内存最快,为什么不让所有进程通信都用共享内存?多出来的是哪部分工作?
小结
知识点
- 进程地址空间互相隔离,无法直接访问对方数据
- 进程间通信需要内核提供通道
- 管道、消息、信号、共享内存各有取舍
- 共享内存最快,但需要额外的同步
参考资料
- Wikipedia(zh):进程间通信:IPC 的各种方式
- Wikipedia(zh):管道:管道
- Wikipedia(zh):共享内存:共享内存
思考题答案(仅供参考)
因为共享内存虽然快,却把“协调”这件事全丢给了使用者:多个进程同时读写同一块内存,必须自己保证它们不会互相覆盖、不会读到写了一半的数据。这就需要锁、信号量等一整套同步机制,写起来容易出错。管道和消息由内核替你把数据搬好、把边界划清,省心得多。所以选哪种,要看更在意速度还是更在意省事——工具没有绝对的好坏,只有是否合适。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪