浅谈Python基础之I/O模型


Posted in Python onMay 11, 2017

一、I/O模型

IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?

这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non-blocking IO是一个东西。这其实是因为不同的人的知识背景不同,并且在讨论这个问题的时候上下文(context)也不相同。所以,为了更好的回答这个问题,先限定一下本文的上下文。
本文讨论的背景是Linux环境下的network IO。

Stevens在文章中一共比较了五种IO Model:

blocking IO(阻塞IO)

nonblocking IO (非阻塞IO)

IO multiplexing (IO多路复用)

asynchronous IO (异步IO)

signal driven IO (信号驱动IO)

由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。

再说一下IO发生时涉及的对象和步骤。

对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:

等待数据准备 (Waiting for the data to be ready)
将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。

二、 blocking IO

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

浅谈Python基础之I/O模型

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network IO来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

三、non-blocking IO

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

浅谈Python基础之I/O模型

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问kernel数据好了没有

注意:

在网络IO时候,非阻塞IO也会进行recvform系统调用,检查数据是否准备好,与阻塞IO不一样,”非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 ‘被' CPU光顾”。即每次recvform系统调用之间,cpu的权限还在进程手中,这段时间是可以做其他事情的,也就是说非阻塞的recvform系统调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态

import time
import socket

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.bind(('127.0.0.1', 8080))
sk.listen(5)
sk.setblocking(False)  #设置套接字为非阻塞模式
while True:
  try:
    print('waiting client connection .......')
    connection, address = sk.accept() # 进程主动轮询
    print("+++", address)
    client_messge = connection.recv(1024)
    print(str(client_messge, 'utf8'))
    connection.close()
  except Exception as e:
    print(e)
    time.sleep(4)

#############################client

import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

while True:
  sk.connect(('127.0.0.1', 8080))
  print("hello")
  sk.sendall(bytes("hello", "utf8"))
  time.sleep(2)
  break

以上实例,服务段端每隔4秒轮询一次,若没有任何客户端链接,则会抛出错误信息,并继续轮询。

非阻塞IO:

优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。

缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成,导致不能实时获取数据,这也会导致整体数据吞吐量的降低。

四、IO multiplexing

IO multiplexing这个词可能有点陌生,有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

浅谈Python基础之I/O模型

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接)

在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

注意:

1.select函数返回结果中如果有文件可读了,那么进程就可以通过调用accept()或recv()来让kernel将位于内核中准备到的数据copy到用户区。

2.select的优势在于可以处理多个连接,不适用于单个连接

import select, socket

sock = socket.socket()
sock.bind(('127.0.0.1', 8080))
sock.listen(5)

sock.setblocking(False)
listen_obj = [sock, ]

while True:
  r, w, e = select.select(listen_obj, [], [])

  for obj in r:
    if obj == sock:
      conn, addr = obj.accept()
      print('conn', conn)
      print('addr', addr)
      listen_obj.append(conn)
    else:
      data = obj.recv(1024)
      print(data.decode('utf8'))
      send_data = input('>>>')
      obj.send(send_data.encode('utf8'))

#############################client


import socket

sock = socket.socket()
sock.connect(('127.0.0.1', 8080))


while True:
  data = input('>>>')
  sock.send(data.encode('utf8'))
  recv_data = sock.recv(1024)
  print(recv_data.decode('utf8'))
sock.close()

以上实例,服务端kernel监听select负责的listen_obj中的所有socket对象。当任何一个socket对象激活,根据其类型判断是否建立通信。

五、asynchronous I/O

linux下的asynchronous IO其实用得很少。先看一下它的流程:

浅谈Python基础之I/O模型

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它收到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

很明显,使用异步IO来编写程序性能会远远高于同步IO,但是异步IO的缺点是编程模型复杂。

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

An asynchronous I/O operation does not cause the requesting process to be blocked;

两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

各个IO Model的比较如图所示:

浅谈Python基础之I/O模型

经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

以上这篇浅谈Python基础之I/O模型就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python 异常处理实例详解
Mar 12 Python
Python 多线程抓取图片效率对比
Feb 27 Python
python实现下载整个ftp目录的方法
Jan 17 Python
Python中最大递归深度值的探讨
Mar 05 Python
Django3.0 异步通信初体验(小结)
Dec 04 Python
Python paramiko 模块浅谈与SSH主要功能模拟解析
Feb 29 Python
python3 使用traceback定位异常实例
Mar 09 Python
Python3操作读写CSV文件使用包过程解析
Apr 10 Python
Python类中的装饰器在当前类中的声明与调用详解
Apr 15 Python
python Django 反向访问器的外键冲突解决
May 20 Python
Node.js 和 Python之间该选择哪个?
Aug 05 Python
python语言time库和datetime库基本使用详解
Dec 25 Python
老生常谈Python进阶之装饰器
May 11 #Python
python 第三方库的安装及pip的使用详解
May 11 #Python
插入排序_Python与PHP的实现版(推荐)
May 11 #Python
Python实现计算两个时间之间相差天数的方法
May 10 #Python
Python开发的实用计算器完整实例
May 10 #Python
Python只用40行代码编写的计算器实例
May 10 #Python
Python实现脚本锁功能(同时只能执行一个脚本)
May 10 #Python
You might like
全国FM电台频率大全 - 20 广西省
2020/03/11 无线电
如何写php程序?
2006/12/08 PHP
PHP扩展模块memcached长连接使用方法分析
2014/12/24 PHP
php计算两个日期相差天数的方法
2015/03/14 PHP
YII2框架使用控制台命令的方法分析
2020/03/18 PHP
idTabs基于JQuery的根据URL参数选择Tab插件
2012/04/11 Javascript
alert中断settimeout计时功能
2013/07/26 Javascript
javascript删除option选项的多种方法总结
2013/11/22 Javascript
javascript数组随机排序实例分析
2015/07/22 Javascript
js实现登陆遮罩效果的方法
2015/07/28 Javascript
Jquery获取第一个子元素简单实例
2016/06/02 Javascript
BootStrap 超链接变按钮的实现方法
2016/09/25 Javascript
详解Vue.js入门环境搭建
2017/03/17 Javascript
微信小程序顶部可滚动导航效果
2017/10/31 Javascript
详解layui中的树形关于取值传值问题
2018/01/16 Javascript
react.js组件实现拖拽复制和可排序的示例代码
2018/08/20 Javascript
Element-UI中Upload上传文件前端缓存处理示例
2019/02/21 Javascript
微信小程序结合mock.js实现后台模拟及调试
2019/03/28 Javascript
vue输入节流,避免实时请求接口的实例代码
2019/10/30 Javascript
javascript实现留言板功能
2020/02/08 Javascript
django实现分页的方法
2015/05/26 Python
Python使用爬虫抓取美女图片并保存到本地的方法【测试可用】
2018/08/30 Python
python使用selenium实现批量文件下载
2019/03/11 Python
django中上传图片分页三级联动效果的实现代码
2019/08/30 Python
canvas进阶之贝塞尔公式推导与物体跟随复杂曲线的轨迹运动
2018/01/10 HTML / CSS
MADE荷兰:提供原创设计师家具
2018/04/03 全球购物
中软国际Java程序员笔试题
2014/07/19 面试题
洗发水广告词
2014/03/13 职场文书
服务员岗位职责
2015/02/03 职场文书
党员干部廉洁自律承诺书
2015/04/28 职场文书
书法社团活动总结
2015/05/07 职场文书
小学推普周活动总结
2015/05/07 职场文书
2015年幼师个人工作总结
2015/10/15 职场文书
python 开心网和豆瓣日记爬取的小爬虫
2021/05/29 Python
mysql事务隔离级别详情
2021/10/24 MySQL
Python中使用Opencv开发停车位计数器功能
2022/04/04 Python