一,IO模型-----为深入了解IO模型,同步,异步,阻塞,非阻塞。
同步(synchronous)IO和异步(asynchronous)IO,阻塞(blocking)IO和非阻塞(non-blocking)IO
1,等待数据准备------waiting for the data to be ready
2,将数据从内核拷贝到进程中------Copying the data from the kernel to the process
二,阻塞IO(blocking IO)
默认情况下socket都是blocking
blocking IO的特点就是:在IO自行的两个阶段(等待数据和拷贝数据两个阶段)都被block了
ps:所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回
出特别指定,所有的IO接口(包括socket接口)都是阻塞型的。
解决IO接口存在的问题
1,在服务器端使用多线程(或多进程),多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接
存在问题:
开启多进程或多线程的方式,在遇到同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而且线程与进程本身也容易进入假死状态
改进方案:
很多程序员可能会考虑使用线程池/进程池,线程池:指在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担执行任务。连接池维持连接和缓存池,尽量重用已有的连接,减少创建和关闭连接的频率,这两种计数都可以很好地降低系统开销,都被广泛应用的很多大型系统,如websphere,tomcat和各种数据库等。
改进后的存在问题
线程池和连接池技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而所谓‘池’始终有其上限,当请求大大超过限时,‘池’构成的系统对外界的响应并不比没有吃的时候效果好多少,所以使用‘池’必须考虑其面临的响应规模,并根据响应规模调整‘池’的大小
***对应上例中的锁面临的可能同时出现同时上千万次客户端请求,‘线程池’/‘进程池’或许可以缓解部分压力,但是不能解决所有问题,总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。
三,非阻塞IO(non-blocking IO)
在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有
/*服务端
from socket import *
import time
s=socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('127.0.0.1',8080))
s.listen(5)
s.setblocking(False) #设置socket的接口为非阻塞
conn_l=[]
while True:
try:
conn,addr=s.accept()
print(addr)
conn_l.append(conn)
except Exception:
for conn in conn_l:
try:
data=conn.recv(1024)
conn.send(data.upper())
except Exception:
continue
/*客户端
from socket import *
c=socket(AF_INET,SOL_SOCKET)
c.connect(('127.0.0.1',8080))
while True:
msg=input('>>:').strip()
if not msg:continue
c.send(msg.encode('utf-8'))
data=c.recv(1024)
print(data.decode('utf-8'))
优点:能够在等待任务完成的时间里干其他活(包括提交其他任务,也就是‘后台’也可以由多个任务在‘同时’执行)
缺点:循环调用recv是大量占用CPU,任务完成响应的延迟增大了。
在上方案中recv()更多的是起到检测‘操作完成’的作用
四,多路复用IO(IO multiplexing)
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的优势在于处理多个连接,不适用于单个连接。
/*/*/*服务端
from socket import *
import select
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8085))
server.listen(5)
server.setblocking(False)
print('starting...')
conn_l=[]
del_l=[]
while True:
try:
print(conn_l)
conn,addr=server.accept()
conn_l.append(conn)
except BlockingIOError:
for conn in conn_l:
try:
data=conn.recv(1024)
conn.send(data.upper())
except BlockingIOError:
pass
except BlockingIOError:
del_l.append(conn)
for obj in del_l:
obj.close()
conn_l.remove(obj)
continue
/*/*/*客户端
from socket import *
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8085))
while True:
msg=input('>>>:').strip()
if not msg:continue
client.send(msg.encode('utf-8'))
data=client.recv(1024)
print(data.decode('utf-8'))
五,异步IO(Asynchronous I/O)
用户进程发起read操作之后,立刻就可以开始做其他事,一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
六,IO模型的分析比较(各个IO的model图的比较)