I/O模型的简介
前置知识
"""
任务的提交方式
同步:就是发生功能调用时,需要等待任务的返回结果,期间不做任何操作。
异步:无需等待任务的返回结果,继续执行程序,任务的返回结果由异步回调机制返回。
任务的提交结果
阻塞:发生功能调用时(如I/O操作),会将当前线程挂起,直至得到返回结果,再将线程激活。
非阻塞:与阻塞的概念是相反的
"""
这里研究的I/O模型都是针对网络的I/O
5种I/O模型
"""
blocking I/O 阻塞I/O
nonblocking I/O 非阻塞I/O
I/O multiplexing I/O多路复用
signal driven I/O 信号驱动I/O(不需要掌握)
asynchronous I/O 异步I/O
"""
对于一个Network I/O (以read为例),它会涉及到两个系统对象。一是调用这个I/O的process(or thread),另一个是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段。
(1)等待数据准备(waitting for data to ready)
(2)将数据从内核拷贝到进程中(copying the data from thekernel to the process)
阻塞I/O模型
在linux中,默认情况下所有的socket都是blocking。
blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了
import socket
IP_ADDRESS = ('0.0.0.0', 9090)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(IP_ADDRESS)
sock.listen(5)
while True:
conn, addr = sock.accept()
while True:
try:
msg = conn.recv(1024)
if not msg: break
print(msg)
conn.send(msg.upper())
except ConnectionResetError as e:
break
conn.close()
在服务端开设多进程或者多线程 进程池线程池 其实还是没有解决IO问题。该等的地方还是得等 ,没有规避
只不过多个人等待的彼此互不干扰。
非阻塞I/O模型
在Linux下,可以设置socket使其变为nonblocking。
在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
"""
服务端
"""
import socket
import time
IP_ADDRESS = ('0.0.0.0', 9090)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(IP_ADDRESS)
sock.listen(5)
# 将network IO由阻塞变为非阻塞
sock.setblocking(False)
read_list = []
del_list = []
while True:
try:
conn, addr = sock.accept()
read_list.append(conn)
except BlockingIOError as e:
"""
非阻塞模式下,没有阻塞状态,若没有连接进来会报错
"""
# time.sleep(0.01)
for conn in read_list:
try:
recv_data = conn.recv(1024)
if not recv_data:
conn.close()
del_list.append(conn)
continue
print(recv_data.decode('utf-8'))
conn.send(recv_data.upper())
except BlockingIOError as e:
continue
except ConnectionResetError as e:
conn.close()
del_list.append(conn)
"""
去掉无用的连接
"""
for conn in del_list:
read_list.remove(conn)
read_list.clear()
"""
客户端
"""
import socket
IP_ADDRESS = ('182.92.59.34', 9090)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect_ex(IP_ADDRESS)
while True:
# msg = input('please enter the message>>>:').strip()
# if not msg: continue
client.send(b'hello world!')
back_msg = client.recv(1024)
print(back_msg.decode('utf-8'))
但是非阻塞IO模型绝不被推荐。
因为长时间占用着CPU并且不干活 让CPU不停的空转,我们实际应用中也不会考虑使用非阻塞IO模型。
I/O多路复用
"""
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。 这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
"""
强调:
1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
结论
select的优势在于可以处理多个连接,不适用于单个连接
"""
服务端
"""
import socket
import select
IP_ADDRESS = ('0.0.0.0', 9090)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(IP_ADDRESS)
sock.listen(5)
sock.setblocking(False)
read_list = [sock, ]
write_list = []
wdata = {}
while True:
r_list, w_list, x_list = select.select(read_list, write_list, [])
for item in r_list:
"""
针对不同的对象,做不同的处理,r_list里面可能是socket对象,也可能是conn对象
"""
if item is sock:
conn, addr = item.accept()
"""将conn加入到监管队列中去"""
read_list.append(conn)
else:
try:
msg = item.recv(1024)
if not msg:
item.close()
read_list.remove(item)
continue
print(msg.decode('utf-8'))
write_list.append(item)
wdata[item] = msg.upper()
except Exception:
item.close()
read_list.remove(item)
for item in w_list:
item.send(wdata[item])
write_list.remove(item)
wdata.pop(item)
"""
客户端
"""
import socket
IP_ADDRESS = ('182.92.59.34', 9090)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect_ex(IP_ADDRESS)
while True:
# msg = input('please enter the message>>>:').strip()
# if not msg: continue
client.send(b'hello world!')
back_msg = client.recv(1024)
print(back_msg.decode('utf-8'))
总结:
"""
监管机制其实有很多
select机制 windows linux都有
poll机制 只在linux有 poll和select都可以监管多个对象 但是poll监管的数量更多
上述select和poll机制其实都不是很完美 当监管的对象特别多的时候
可能会出现 极其大的延时响应
epoll机制 只在linux有
它给每一个监管对象都绑定一个回调机制
一旦有响应 回调机制立刻发起提醒
针对不同的操作系统还需要考虑不同检测机制 书写代码太多繁琐
有一个人能够根据你跑的平台的不同自动帮你选择对应的监管机制
selectors模块
"""
异步I/O
"""
异步IO模型是所有模型中效率最高的 也是使用最广泛的
相关的模块和框架
模块:asyncio模块
异步框架:sanic tronado twisted
速度快!!!
"""
import threading
import time
import asyncio
async def hello():
print(f'hello world {threading.current_thread().getName()}')
"""使用sleep来模拟IO操作"""
time.sleep(1)
print(f'hello world {threading.current_thread().getName()}')
loop = asyncio.get_event_loop()
task = [hello(), hello()]
loop.run_until_complete(asyncio.wait(task))
loop.close()
注意:
"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
I/O模型的比较分析
"""
经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。
"""
selectors模块
select,poll,epoll