python高级技术(IO模型)
一 IO模型介绍
Linux环境下的network IO
* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路复用
* signal driven IO 信号驱动IO
* asynchronous IO 异步IO
由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。
再说一下IO发生时涉及的对象和步骤。对于一个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(UDP协议)
send虽然也有IO行为,但是不在我们的考虑范围
send是主动性的
二 阻塞IO(blocking IO)
之前写的都是阻塞IO模型,协程除外 * 在服务端开设多个进程或者多个线程,进程池线程池,其实还是没有解决IO问题 该等的地方还是得等,没有规避 只不过多个人等待的彼此互不干扰
对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。
总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。
三 非阻塞IO(non-blocking IO)
服务端:
import socket import time server = socket.socket() server.bind(('127.0.0.1', 8080)) server.listen(5) server.setblocking(False) # 将所有的网络阻塞变为非阻塞 r_list = [] del_list = [] while True: try: conn, addr = server.accept() r_list.append(conn) except BlockingIOError as e: # time.sleep(0.1) # 用了,就是阻塞了 # print('列表的长度:', len(r_list)) 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 as e: continue # 继续下一个通信对象有没有数据 except ConnectionResetError as e: conn.close() del_list.append(conn) # 删除无用的链接 for conn in del_list: r_list.remove(conn) del_list.clear() # while True: # try: # data = conn.recv(1024) # if len(data) == 0: # break # conn.send(data.upper()) # except ConnectionResetError as e: # break # conn.close()
客户端:
import socket client = socket.socket() client.connect(('127.0.0.1', 8080)) while True: client.send(b'hello word') data = client.recv(1024) print(data.decode('utf-8'))
总结:
虽然非阻塞IO可以实现并发
但是该模型会长时间占用者CPU并且不干活,让CPU不停的空转
实际应用中也不会考虑使用非阻塞IO模型
任何的技术点都有它存在的意义
实际应用或者是思想借鉴
四 多路复用IO(IO multiplexing)
服务端:
import socket import select server = socket.socket() server.bind(('127.0.0.1', 8081)) server.listen(5) server.setblocking(False) read_list = [server] # res = select.select(read_list, [], []) ''' 帮你监管 一旦有人来了 立刻给你返回对应的监管对象 ''' # print(res) # ([<socket.socket fd=336, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>], [], []) while True: # 不断的监测 r_list, w_list, x_list = select.select(read_list, [], []) for i in read_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(res.upper())
客户端:
import socket client = socket.socket() client.connect(('127.0.0.1', 8081)) while True: client.send(b'hello word') data = client.recv(1024) print(data)
当监管的对象只有一个的时候,其实IO多路复用连阻塞IO都比不上
但是IO多能复用可以一次性监管很多个对象
server=socket.socket()
conn,addr=server.accept()
监管机制是操作系统本身的机制,如果想要用该监管机制(select)
需要导入对应的select模块
总结:
监管机制有很多
select机制 windows linux都有
poll机制 只在linux有 poll和select都可以监管多个对象,但poll监管的数量更多
上述select和poll机制不完美,当监管的对象特别多的时候
可能会出现极大地延时响应
epoll机制 只在linux有
它给每一个监管对象都绑定一个回调机制
一旦响应,回调机制立刻发起提醒
针对不同的操作系统需要考虑不同的监测机制,selectors模块帮你根据不同的平台自动对应相应
的监测机制
五 异步IO(Asynchronous I/O)
示例:
import threading import asyncio @asyncio.coroutine def hello (): print('hello world{}'.format(threading.current_thread())) yield from asyncio.sleep(1) # 换成真正的IO操作 print('hello world{}'.format(threading.current_thread())) loop = asyncio.get_event_loop() tasks = [hello(), hello()] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
异步IO模型是所有模型中效率最高的,也是使用最广泛的
相关的模块和框架
模块:asyncio模块
异步框架:sanic tronado twisted
速度快
六 IO模型比较分析
经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。