python并发编程之IO模型
一,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图的比较)