进程间通信的历史与未来

- START -

我们都知道线程是共享内存空间的,因此不会发生所谓的通信,而进程则存在如何防止多进程同时访问数据的排他控制问题。

5 种进程间通信的方式

  • 管 道
  • SysV IPC
  • TCP 套接字
  • UDP 套接字
  • UNIX 套接字

管道

  所谓管道,就是能够从一侧输入,然后从另一侧读取的文件描述符对。Shell 中的管道也是通过这一方式实现的。

  文件描述符在每个进程中是独立存在的,但创建子进程时会继承父进程中所有的文件描述符,因此它可以用于在具有父子、兄弟关系的进程之间进行通信。

  例如,在具有父子关系的进程之间进行管道通信时,可以按下列步骤操作。在这里为了简单期间,我们只由子进程向父进程进行通信。

  1. 首先,使用 pipe 系统调用,创建一对文件描述符。下面我们将读取一方的文件描述符称为 r,将写入一侧的文件描述符称为 w
  2. 通过 fork 系统调用创建子进程。
  3. 在父进程一方将描述符 w 关闭。
  4. 在子进程一方将描述符 r 关闭。
  5. 在子进程一方将要发送给父进程的数据写入描述符 w
  6. 在父进程一方从描述符 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,远程过程调用)呢,这一切有没有联系呢?希望本文可以引发你的思考

原文发布于微信公众号 - 随心DevOps(heart-devops)

原文发表时间:2017-12-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOS开发攻城狮的集散地

Run loop

23510
来自专栏Java帮帮-微信公众号-技术文章全总结

Mybatis_day02

Mybatis第二天 课程安排 对订单商品数据模型进行分析 高级映射: 实现一对一、一对多,多对多查询 延迟加载 查询缓存 一级缓存 二级缓存(了解mybati...

3318
来自专栏用户2442861的专栏

2014年10月22日网易游戏数据库系统工程师初面

http://blog.csdn.net/hellen1900/article/details/40421911

291
来自专栏王小雷

Hive基础(1)

Hive基础(1) Hive的HQL(2) 1. Hive并不是分布式的,它独立于机器之外,类似于Hadoop的客户端。 2. 元数据和数据的区别,前者如表名、...

1987
来自专栏IT笔记

Tomcat优化之配置线程池

简介 线程池作为提高程序处理数据能力的一种方案,应用非常广泛。大量的服务器都或多或少的使用到了线程池技术,不管是用Java还是C++实现,线程池都有如下的特点:...

2988
来自专栏数据和云

深入解析:由SQL解析失败看开发与DBA的性能之争

李华 云和恩墨高级技术顾问 以下案例来自大讲堂的一次分享,从这个案例中我们可以了解“错误的SQL”可能对数据库产生的种种影响。如何找到这些错误的、解析失败的S...

2905
来自专栏数据和云

MySQL row格式的两个问题

作者简介: ? 刘伟 云和恩墨开源解决方案事业部首席架构师 多年一线互联网企业DBA经历,对MySQL、NoSQL,PostgreSQL等各类开源数据库均有涉猎...

3436
来自专栏解Bug之路

MySQL多版本并发控制机制(MVCC)-源码浅析 顶

作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾。<<事务处理-概念与技术>>诚然讲的非常透彻,但只能提纲挈领,不能让你玩转...

942
来自专栏性能与架构

Redis 客户端缓冲区 output buffer

output buffer是 Redis 为 client 分配的缓冲区,Redis的每个连接都配有自己的缓冲区空间 这个client包括: (1)真正的用...

34115
来自专栏京东技术

Vitess告诉你两阶段提交到底长啥样

● 原子性:一个事务对状态的改变是原子的,要么都发生,要么都不发生,这些改变包括数据库的改变、消息以及对转换器的操作。

1392

扫码关注云+社区