进程间通信(IPC)是操作系统提供的一种允许进程之间交换数据,从而实现通信的机制。
process1<----进程间通信----->process2
它允许进程间相互通信,这种通信可能是让一个进程知道另一个进程中某个事件已经发生,或者是数据从一个进程转移到另一个进程。
进程间通信的方法:
- 管道
- 消息队列
- 共享内存
- 信号量
- Socket
管道
管道是在 UNIX 操作系统中引入的。
ls |grep "bash"
ls 就是列出当前目录下的所有文件和文件夹,grep 是一个文本处理工具,用于查找出符合条件的字符串,这里表示查找出包含 "bash" 的字符串。
两个命令中的竖线 "|" 就是管道,它的作用是把前一个命令的输出作为后一个命令的输入。
所以这条命令的意思就是,筛选出当前目录下,名字中有 "bash" 的文件和文件夹。有时候文件数量特别多,这条命令可以帮助你快速进行文件查找。
进程中也可以使用管道进行通信,管道通常会缓存来自一个进程输出的数据,直到另一个进程接收到该数据为止。
从图中可以看出,管道是单向的数据通道,类似于键盘流。可以使用两个管道在两个进程之间创建双向数据通道。
如果数据没有被拿走,则第一个进程会一直等待,不能做其他事。所以管道这种通信方式很简便,但是效率很低,不能用于其他很复杂的场景。
消息队列
管道中第一个进程必须等待数据被读取,这种方法使得进程间通信效率低下。于是消息队列就登场了。
Message Queue
两个不同进程可以相互连接,它们只需要对消息队列进行操作即可。
A 进程将数据写入消息队列,不用等待,就去做自己的事情了。B 进程来了也不管 A 进程在不在,直接从消息队列里读取数据即可。
有时候两个进程会进行频繁地对消息队列进行存取,就会进行大量地拷贝数据操作。还有数据占内存比较大时,拷贝数据操作就更加耗时。
而且如果两个进程存放和读取数据的速度差别比较大,则消息队列会处于常满或者常空的情况
共享内存
针对消息队列拷贝数据耗时,以及存放和读取数据的速度差别大的问题,共享内存又登场了。
共享内存就是可以由多个进程同时访问的内存,不同对数据进行拷贝,两个进程约定一个共同空间,直接在该空间对数据进行存取,实现进程间通信。
可以这似乎和每个进程都有一个独立的内存空间相矛盾,明明进程的内存空间相互独立,怎么还存在共享内存这个东西?
这是因为当操作系统要执行一个进程的时候,分配给进程的内存是虚拟内存,两个进程的虚拟内存是相互独立的,虚拟内存可以比实际物理内存大很多。
虚拟内存通过分页的方式映射到物理内存,可以让两个进程的虚拟内存映射到相同物理内存中,这就是共享内存。
因为多个进程都可以结对访问共享内存,那么肯定会存在一个问题:
两个或多个进程读写某些共享数据时,遇到会遇到多进程竞争内存的情况,导致读写的数据错误。
信号量
为了解决多进程竞争共享内存的情况,可以使用信号量(Semaphore)。
多进程竞争共享内存是什么?举个简单例子,
假设有 2 个用户使用 1 台打印机,2 个用户同时开始打印资料,则 2 个用户的资料会相互重叠。为了避免这种情况,就需要使用信号量来保护共享内存。
信号量可以看做是一个计数器,初始值表示允许同时访问共享内存的最多进程数。当有一个进程访问共享内存,信号量减一。
当信号量为零时,锁定共享内存,即不允许其他进程访问共享内存。进程取消访问共享内存,信号量加一。
再举个简单例子,
假设有 5 个用户使用 3 台打印机,信号量初始值为 3,每当有一个用户使用打印机,信号量减一,则当有 3 个用户使用打印机,第四个人就无法使用打印机,只有当有人不使用打印机,信号量不为零时,第四个人才可以使用。
如果信号量初始值为 1,那么只允许一个进程访问共享内存,有个专门的名字,叫互斥锁。
Socket
面介绍了几个进程间通信的方法,它们有一个共同的特点,就是多个进程在本地进行通信。
要想在不同的主机上进行通信,就需要用到 Socket(套接字),用于在客户端和服务器之间通过网络进行通信。
Socket 是 TCP/IP 协议中应用层和传输层之间的一个抽象层,它把TCP/IP协议族复杂的操作抽象为几个简单的接口供应用层调用,从而实现进程在网络中通信。
Socket 是一种 ”打开—读/写—关闭” 模式的实现,服务器和客户端的进程各自维护一个 ”文件”,在建立连接后,可以向自己文件写入内容供对方读取或者读取对方内容,从而实现通信,通信结束时关闭文件。