7.1 进程间通信(IPC)
通信基础
一般来讲进程是独立运行的, 不会影响其它进程. 进程相当的复杂, 有着多种功能. 但是这样产生了一些问题:
重复实现
就比如聊天软件和邮箱软件, 它们都需要数据库. 那可能它们都得在自己的应用里实现数据库了.
低效实现
可能聊天软件将自己的数据库优化的很好, 但是邮箱软件更倾向于组件的美观, 数据库随便整了个.
据说微信聊天记录用的是 sqlite, 优化的非常好
没有信息共享
邮箱和聊天软件都需要监控系统资源信息, 如果没有信息共享, 即使邮箱软件计算了一遍, 聊天软件也需要计算一遍
如果可以协作的话, 那么上面的问题就可以很好地解决了. 聊天软件可以将自己的数据库单独抽离出来, 这样两个软件的数据库都很高效了. 并且之后数据库还可以复用.
不同进程专注于特定的计算任务, 性能更好; 直接共享已经计算好的数据, 避免重复计算.
进程间通信(Inter-process Communication, IPC) 因此诞生了.
两个或者多个不同的进程可以通过 IPC 来实现协作, 通过内核或者其它的共享资源进行通信. 通信的内容叫做消息.
共享内存通信
系统内核为两个进程映射相同的内存区域, 这就要求发送方不能覆盖掉未读取的数据, 接收方不能读取别的数据.
这样存在一些问题, 那就是这需要进行轮询. 如果让操作系统使用中断等方法来提醒接收方读取数据实现可能就很复杂了, 只能让接收方轮询某块内存有没有写入新东西. 并且当接收方在处理消息时, 如果此时共享内存满了, 发送方是写不了的, 也只能轮询内存可不可写.
这样的实现导致资源浪费, 而且每隔一定时间检查导致时延长.
消息传递
建立一个消息系统, 使用中间层(如内核)保证通信时延, 仍然可以使用共享内存传递数据
好处: 低时延(消息立刻转发), 不浪费计算资源
基本操作
发送消息 Send(message)
接收消息 Recv(message)
如果进程 P 和进程 Q 想要通过消息传递进行通信, 需要建立一个通信连接, 然后使用 Send/Recv 接口进行消息传递.
直接通信
每一个进程都有一个唯一的标识符, 在 Send 和 Recv 的时候带上标识符就可以了.
Send(P, message)给进程 P 发送一个消息
Recv(Q, message)从进程 Q 接收一个消息
直接通信的连接的建立是自动的(通过标识符). 一个连接唯一对应一对进程, 一对进程也只会有一个连接. 连接大部分是双向的, 也可以是单向的.
不过这个也存在一个问题. 就是假如 send 的时候, 另一方没有 Recv 怎么办.
间接通信
在间接通信中, 消息的发送和接收都需要经过一个信箱, 这个信箱相当于一个聊天群, 所有在群里的人都可以接收消息.
每个信箱都有标识符, 发送者之后只需要往信箱发送消息, 接收者在信箱读取消息就可以了.
这样, 当 Send 的时候另一方没有 Recv 时也没有问题, 等到另一方 Recv 就会去信箱里读消息.
间接通信的进程间连接发生在共享一个信箱时, 每对进程可以有多个连接(共享多个信箱), 连接同样可以是单向也可以是双向
间接进程间通信会创建一个新的信箱, 之后通过信箱发送和接收消息, 完成之后消耗信箱.
这种方式类似于订阅发布的设计模式, 并且具有未来事件的特性, 发送方在信箱里触发一个事件, 如果没有接收者那就存着, 等接收者上线了就发给它.
不过也存在一个问题, 假如我开了多进程来作为消费者接收任务, 每个任务应该只处理一次, 但是像这样通信的话, 所有消费者都会接收到同一个任务.
可能的解决方法大概有: 让一个信箱最多被两个进程共享; 同一时间只允许最多一个进程接收消息; 让消息系统随便选一个接收者, 并通知发送者谁接收了.
同步还是异步?
消息的传递可以是阻塞的, 也可以是非阻塞的.
阻塞的消息传递是指发送方/接收方一直处于阻塞状态, 等待消息的发出/到来. 这种也叫 同步 通信. 同步通信通常具有更低的时延和易用的编程模型.
非阻塞的消息传递是指发送方/接收方不等待执行结果, 这种叫 异步通信 . 异步通信的 带宽 一般更高, 也就是说一段时间内消息的传递会更频繁.
超时机制
为了避免一直阻塞, 需要一个超时机制. 当接收方方一定时间内没有响应就不等了, 下次再发送.
缓冲区
当接收方正在处理一个消息时, 又来了一个消息. 这个时候就需要决定怎么处理这个消息了. 一般有这三种方法:
通信连接可以选择保留住还没有处理的消息
零容量 通信连接本身不缓冲消息, 发送方只能阻塞等待接收方接收消息
有限容量 连接可以缓冲最多 N 个消息, 缓冲区满后发送方只能阻塞了
无限容量 连接可以缓冲任意个消息, 发送方几乎不需要阻塞
管道
Unix 管道
管道是 Unix 等宏内核系统中非常重要的进程间通信机制
管道(Pipe)是两个进程间的一根通信通道, 一端往里投递消息, 另一端接收. 管道也是一种间接消息传递方式, 通过共享一个管道来建立连接.
管道特点
单向通信, 当缓冲区满时就阻塞.
一个管道有且仅有两个端口, 一个负责输入(发送数据), 一个负责输出(接收数据)
数据本身没有类型, 只是字节流.
优点与问题
优点: 针对简单通信场景十分有效
缺点: 没有消息类型, 接收方需要对消息内容进行解析. 缓冲区的大小预先分配且固定; 只能单向通信; 最多两个进程间通信.
可以试试在 go 里面, 一个纤程同时读和写, 看看会发生什么.
消息队列
消息队列是带类型的消息传递, 以链表的方式组织消息. 任何有权限的进程都可以访问队列, 进行读或写; 支持异步通信(非阻塞).
消息由类型和数据组成, 类型用一个整形表示, 具体是什么由用户自己决定.
消息队列也是一种间接消息传递方式, 通过共享一个队列来建立连接.
消息队列遵循 FIFO, 先进先出. 在队列尾部写入, 在队列首部读取. 并且可以按照类型查询, 如果类型为 0 那么返回第一个消息, 如果不为 0 返回对应的第一个消息.