进程间通信的历史与未来
- START -
我们都知道线程是共享内存空间的,因此不会发生所谓的通信,而进程则存在如何防止多进程同时访问数据的排他控制问题。
5 种进程间通信的方式
- 管 道
- SysV IPC
- TCP 套接字
- UDP 套接字
- UNIX 套接字
管道
所谓管道,就是能够从一侧输入,然后从另一侧读取的文件描述符对。Shell 中的管道也是通过这一方式实现的。
文件描述符在每个进程中是独立存在的,但创建子进程时会继承父进程中所有的文件描述符,因此它可以用于在具有父子、兄弟关系的进程之间进行通信。
例如,在具有父子关系的进程之间进行管道通信时,可以按下列步骤操作。在这里为了简单期间,我们只由子进程向父进程进行通信。
- 首先,使用
pipe
系统调用,创建一对文件描述符。下面我们将读取一方的文件描述符称为r
,将写入一侧的文件描述符称为w
。 - 通过
fork
系统调用创建子进程。 - 在父进程一方将描述符
w
关闭。 - 在子进程一方将描述符
r
关闭。 - 在子进程一方将要发送给父进程的数据写入描述符
w
。 - 在父进程一方从描述符
r
中读取数据。
笔者直接上代码演示:
#!/usr/bin/env python
#coding:utf8
import multiprocessing
import time
def process_one(pipe):
while True:
for i in range(3):
print "Process-one send: {0}".format(i)
pipe.send(i)
time.sleep(1)
def process_two(pipe):
while True:
print "Process-two received: {0}".format(pipe.recv())
time.sleep(1)
# 创建一个管道
pipe = multiprocessing.Pipe()
# pipe 对象:
# (<read-write Connection, handle 5>, <read-write Connection, handle 6>)
p1 = multiprocessing.Process(target=process_one, args=(pipe[0],))
p2 = multiprocessing.Process(target=process_two, args=(pipe[1],))
# 开始子进程
p1.start()
p2.start()
# 等待,直到子进程结束
p1.join()
p2.join()
结果
Process-one send: 0
Process-two received: 0
Process-one send: 1
Process-two received: 1
Process-one send: 2
Process-two received: 2
Process-one send: 0
Process-two received: 0
Process-one send: 1
Process-two received: 1
Process-one send: 2
Process-two received: 2
...
SysV IPC
UNIX 的 System V (Five) 版本引入了一组称为 SysV IPC 的进程间通信 API,其中 IPC 就是 Inter Process Communication (进程间通信)的缩写。
SysV IPC 包括下列 3 种通信方式。
- 消息队列
- 信号量
- 共享内存
消息队列是一种用于进程间通信的手段。管道只是一种流机制,每次写入数据的长度等信息是无法保存的,相对的,消息队列则可以保存写入消息的长度。
信号量(semaphore)是一种带有互斥计数器的标志(flag)。这个词原本是荷兰语「旗语」的意思,在信号量中可以设定对某种「资源」同时访问数量的上限。
共享内存是一块在进程间共享的内存空间。通过将共享内存空间分配到自身进程内存空间中(attach)的方式来访问。由于对共享内存的访问并没有进行排他控制,因此无法避免一些偶发性问题,必须使用信号量等手段进行保护。
不过,SysV IPC 有一个资源泄露的问题:由于 SysV IPC 的通信路径能够跨进程访问,因此在使用时需要向操作系统申请分配才能进行通信,通信结束之后还必须显式的销毁,如果忘记销毁的话,就会在操作系统中留下垃圾。
管道之类的方式,则在其所属进程结束的同时会自动销毁,因此比 SysV IPC 要更加易用。
其次,学习使用新的 API 要花一些精力,但结果也只能用在一台电脑上的进程间通信中,真的让人没什么动力去学呢。
大家可以在 Linux 中参考一下:
# man svipc
套接字
System V 所提供的进程间通信手段是 SysV IPC,相对的,BSD 则提供了套接字的方式。和其他进程间通信方式相比,套接字有一些优点:
- 通信对象不仅限于同一台计算机,或者说套接字本身主要就是为了计算机之间的通信而设计的。
- (和 SysV IPC 不同)套接字也是一种文件描述符,可进行一般的输入输出。尤其是可以使用 select 系统调用,在通常 I/O 的同时进行「等待」,这一点非常方便。
- 套接字在进程结束后会由操作系统自动释放,因此无需担心资源泄漏的问题。
- 套接字(由于其优秀的设计)从很早开始就被吸收进 System V 等系统了,因此在可移植性方面的顾虑较少。
现在网络几乎完全依赖于套接字。各位所使用的几乎所有的服务的通信都是基于套接字实现的,这样说应该没什么大问题。
套接字分很多种,其中具有代表性的包括:
- TCP 套接字
- UDP 套接字
- UNIX 套接字
TCP(Transmission Control Protocol,传输控制协议)套接字和 UDP(User Datagram Protocol,用户数据协议)套接字都是建立在 IP(Internet Protocol,网际协议)协议之上的上层网络通信套接字。这两种套接字都可用于以网络为媒介的结算机通信。但它们在性质上有一些区别。
TCP 套接字是一种基于连接的、具备可靠性的数据流通信套接字。所谓基本连接,是指通信的双方是固定的;而所谓具备可靠性,是指能够侦测数据发送成功或是失败(出错)的状态。
所谓数据流通信,是指发送的数据是作为字节流来处理的,和通常的输入输出一样,不会保存写入的数据长度信息。
看了上面的内容,大家可能觉得这些都是理所当然的。我们和 UDP 套接字对比一下,就能够理解其中的区别了。
UDP 套接字和 TCP 套接字相反,是一种能够无需连接进行通信、但不具备可靠性的数据通信套接字。所谓能够无需连接进行通信,是指无需固定连接到指定对象,可以直接发送数据;不具备可靠性是指可能会出现中途由于网络状况等因素导致发送数据丢失的情况。
在数据通信中,发送的数据再原则上是能够保存其长度的。但是,在数据过长等情况下,发送的数据可能会被分割。
先不说无连接通信这一点,UDP 和其他一些性质可能会让大家感到非常难用。这是因为 UDP 几乎是原原本本直接使用了作为其基础的 IP 协议。相反 TCP 为了维持可靠性,在 IP 协议之上构建了各种机制。UDP 的特点是结构简单,对系统产生的负荷也较小。
因此,在语音通信(如 IP 电话等)中一般使用 UDP,因为通信的性能比数据传输的可靠性要更加重要,也就是说,相比通话中包含少许杂音来说,还是保证较小的通话延迟要更加重要。
TCP 套接字和 UDP 套接字都是通过 IP 地址和端口号来进行工作的。例如,http 协议中的 http://www.google.com:80/
就表示与 www.google.com
(IP 地址为:31.13.71.7
)所代表的计算机的 80
端口建立连接.
UNIX 套接字
同样是套接字,UNIX 套接字和 TCP、UDP 套接字相比,可以算是一个异类。基于 IP 的套接字一般是通过主机名和端口号来识别通信对象的,而 UNIX 套接字则是在 UNIX 文件系统上创建一个特殊文件,并用该文件的路径进行识别。由于这种方式使用的是文件系统,因此大家可以看出,UNIX 套接字只能用于同一台计算机上的进程间通信。
UNIX 套接字并不是基于 IP 的套接字,它可用于向一台计算机上其他进程提供服务的某种服务程序。
最后
在进程通信手段中,套接字算是非常好用的,但是即便如此,在考虑对工作进行「委派」时,其易用性还并不理想。套接字本来是为网络服务器的实现而设计的,但作为构建分布式应用程序的手段来说,还是太原始了。
MQ(Message Queue,消息队列)就是为了解决这个问题而诞生的,RPC(Remote Procedure Call,远程过程调用)呢,这一切有没有联系呢?希望本文可以引发你的思考
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- kubernetes(十五) kubernetes 运维
- JVM层GC调优(下)
- pytest封神之路第一步 tep介绍
- kubernetes(十六) k8s 弹性伸缩
- Mockito鸡尾酒第一杯 Java单测Mock
- kubernetes(十七) Helm V3 入门到放弃
- Dockerfile文件万字全面解析
- Go测试开发(一) 怎么写Go代码
- kubernetes(十八)集群网路
- Nginx性能监控与调优
- pytest封神之路第二步 132个命令行参数用法
- Jumpserver2.2部署文档
- Golang多线程简单斗地主
- Tomcat性能监控与调优
- Vue+SpringBoot项目实战(一) 搭建环境