python并发编程之IO模型
目录:
- 一 、IO模型介绍
- 二、 阻塞IO(blocking IO)
- 三 、非阻塞IO(non-blocking IO)
- 四 、多路复用IO(IO multiplexing)
- 五 、异步IO(Asynchronous I/O)
- 六 、IO模型比较分析
- 七 、selectors模块
一、IO模型介绍
""" 我们这里研究的IO模型都是针对网络IO的 Stevens在文章中一共比较了五种IO Model: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO * asynchronous IO 异步IO 由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。 """
对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段:
#1)等待数据准备 (Waiting for the data to be ready) #2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)
同步异步
阻塞非阻塞
常见的网络阻塞状态:
accept
recv
recvfrom
send虽然它也有io行为 但是不在我们的考虑范围
二、阻塞IO(blocking IO)
""" 我们之前写的都是阻塞IO模型 协程除外 """
示例图:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。
而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。 所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
几乎所有的程序员第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的,使用这些接口可以很方便的构建服务器/客户机的模型。然而大部分的socket接口都是阻塞型的。如下图
ps:所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。
示例:
import socket server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: data = conn.recv(1024) if len(data) == 0:break print(data) conn.send(data.upper()) except ConnectionResetError as e: break conn.close()
# 在服务端开设多进程或者多线程 进程池线程池 其实还是没有解决IO问题 该等的地方还是得等 没有规避 只不过多个人等待的彼此互不干扰
三、非阻塞IO(non-blocking IO)
示例图:
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。
也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
非阻塞IO示例:
服务端:
import socket import time server = socket.socket() server.bind(('127.0.0.1', 8081)) server.listen(5) server.setblocking(False) # 将所有的网络阻塞变为非阻塞 r_list = [] del_list = [] while True: try: conn, addr = server.accept() r_list.append(conn) except BlockingIOError: # time.sleep(0.5) # 打开该行注释纯属为了方便查看效果 print('列表的长度:',len(r_list)) # print('做其他事') for conn in r_list: try: data = conn.recv(1024) # 没有消息 报错 if len(data) == 0: # 客户端断开链接 conn.close() # 关闭conn # 将无用的conn从r_list删除 del_list.append(conn) continue conn.send(data.upper()) except BlockingIOError: continue except ConnectionResetError: conn.close() del_list.append(conn) # 挥手无用的链接 for conn in del_list: r_list.remove(conn) del_list.clear()
客户端:
import socket client = socket.socket() client.connect(('127.0.0.1',8081)) while True: client.send(b'hello world') data = client.recv(1024) print(data)
总结:
优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。
缺点:
#该模型会 长时间占用着CPU并且不干活 让CPU不停的空转(并不推荐该模型)
四、多路复用IO(IO multiplexing)
示例图:
当监管的对象只有一个的时候 其实IO多路复用连阻塞IO都比比不上!!!
但是IO多路复用可以一次性监管很多个对象
监管机制是操作系统本身就有的 如果你想要用该监管机制(select)
需要你导入对应的select模块
示例代码:
服务器端:
import socket import select server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) server.setblocking(False) read_list = [server] while True: r_list, w_list, x_list = select.select(read_list, [], []) """ 帮你监管 一旦有人来了 立刻给你返回对应的监管对象 """ # print(res) # ([<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>], [], []) # print(server) # print(r_list) for i in r_list: # """针对不同的对象做不同的处理""" if i is server: conn, addr = i.accept() # 也应该添加到监管的队列中 read_list.append(conn) else: res = i.recv(1024) if len(res) == 0: i.close() # 将无效的监管对象 移除 read_list.remove(i) continue print(res) i.send(b'heiheiheiheihei')
客户端:
import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: client.send(b'hello world') data = client.recv(1024) print(data)
总结:
""" 监管机制其实有很多 select机制 windows linux都有 poll机制 只在linux有 poll和select都可以监管多个对象 但是poll监管的数量更多 上述select和poll机制其实都不是很完美 当监管的对象特别多的时候 可能会出现 极其大的延时响应 epoll机制 只在linux有 它给每一个监管对象都绑定一个回调机制 一旦有响应 回调机制立刻发起提醒 针对不同的操作系统还需要考虑不同检测机制 书写代码太多繁琐 有一个人能够根据你跑的平台的不同自动帮你选择对应的监管机制 selectors模块 """
五、异步IO(Asynchronous I/O)
示例图:
""" 异步IO模型是所有模型中效率最高的 也是使用最广泛的 相关的模块和框架 模块:asyncio模块 异步框架:sanic tronado twisted 速度快!!! """
示例代码:
import threading import asyncio @asyncio.coroutine def hello(): print('hello world %s'%threading.current_thread()) yield from asyncio.sleep(1) # 换成真正的IO操作 print('hello world %s' % threading.current_thread()) loop = asyncio.get_event_loop() tasks = [hello(),hello()] loop.run_until_complete(asyncio.wait(tasks)) loop.close() ''' 执行结果 hello world <_MainThread(MainThread, started 5300)> hello world <_MainThread(MainThread, started 5300)> hello world <_MainThread(MainThread, started 5300)> hello world <_MainThread(MainThread, started 5300)> '''
六、I/O模型的比较
示例图:
原文地址:https://www.cnblogs.com/baicai37/p/12794520.html
- 老司机教你部署Cowrie蜜罐
- 时间序列预测如何变成有监督学习问题?
- 追踪溯源 | 希拉里邮箱泄露事件
- DNS Tunneling及相关实现
- 创建代码生成器可以很简单:如何通过T4模板生成代码?[下篇]
- 创建代码生成器可以很简单:如何通过T4模板生成代码?[上篇]
- 从Trace和Debug来看条件编译(Conditional Compilation)
- 解码针对工业工程领域的网络攻击 Operation Ghoul「食尸鬼行动」
- EndpointAddress——不只是一个Uri[上篇]
- EndpointAddress——不只是一个Uri[下篇]
- ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起
- ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法
- ASP.NET Core中的缓存[1]:如何在一个ASP.NET Core应用中使用缓存
- ASP.NET Core的路由[5]:内联路由约束的检验
- 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 数组属性和方法
- js的join split startsWith endsWith
- 详解length charAt indexOf lastIndexOf includes concat slice substring substr详解
- sort实现
- border-radius
- USACO Training Section 1.1黑色星期五Friday the Thirteenth
- background-clip默认从border开始的哈
- background-origin默认从padding-box开始的哈
- USACO Training Section 1.1 坏掉的项链Broken Necklace
- 简易通讯录类实现原理
- 盒子模型box-sizing属性的用法
- JeecgBoot配置及前后端分离HelloWorld项目
- POJ 3122 Pie
- POJ 2456 Aggressive cows
- css内容 元素 元素空间的练习