IO阻塞问题
打开文件描述符表
TCP socket读缓冲区写缓冲区
要获得响应数据,就要从读缓冲区拷贝过来。同样的要通过socket发送数据,也要先把数据拷贝到写缓冲区才行。所以,问题出现了:
用户程序想要读数据的时候,读缓冲区里未必有数据,想发送数据的时候,写缓冲区里也未必有空间。那怎么办?
阻塞式IO
阻塞式IO
使用阻塞式IO,要处理一个socket就要占用一个线程。等这个socket处理完成才能接手下一个,这在高并发场景下会加剧调度开销。
非阻塞式IO
非阻塞式IO忙等待
IO多路复用
IO多路复用忙等待
selectpollepoll
select
select
每次调用select都要传递所有的监听集合即便有fd就绪了,也需要遍历整个监听集合,来判断哪个fd是可操作的
poll
poll
epoll
epoll
epoll_create1一个epollepoll_ctlevent dataepoll_wait
通过IO多路复用,线程再也不用为等待某一个socket,而阻塞或空耗CPU。并发处理能力因而大幅提升。
IO多路复用结合协程
但是IO多路复用也并非没有问题,例如:一个socket可读了,但是这回只读到了半条请求,也就是说需要再次等待这个socket可读。在继续处理下一个socket之前,需要记录下这个socket的处理状态。下一次这个socket可读时,也需要恢复上次保存的现场,才好继续处理。
也就是说,在IO多路复用中实现业务逻辑时,我们需要随着事件的等待和就绪,而频繁的保存和恢复现场,这并不符合常规的开发习惯。如果业务逻辑比较简单还好,若是比较复杂的业务场景,就有些悲剧了。
既然业务处理过程中,要等待事件时,需要保存现场并切换到下一个就绪的fd。而事件就绪时,又需要恢复现场继续处理。那岂不是很适合使用协程?
在IO多路复用这里,事件循环依然存在,依然要在循环中逐个处理就绪的fd,但处理过程却不是围绕具体业务,而是面向协程调度。
如果是用于监听端口的fd就绪了,就建立连接创建一个新的fd,交给一个协程来负责, 协程执行入口就指向业务处理函数入口,业务处理过程中,需要等待时就注册IO事件,然后让出,这样,执行权就会回到切换到该协程的地方继续执行。如果是其它等待IO事件的fd就绪了,只需要恢复关联的协程即可。
协程拥有自己的栈,要保存和恢复现场都很容易实现。这样,IO多路复用这一层的事件循环,就和具体业务逻辑解耦了。
可以把read、write、connect等可能会发生等待的函数包装一下,在其中实现IO事件注册与主动让出。这样在业务逻辑层面,就可以使用这些包装函数,按照常规的顺序编程方式,来实现业务逻辑了。
这些包装函数在需要等待时,就会注册IO事件,然后让出协程,这样我们在实现业务逻辑时,就完全不用担心保存与恢复现场的问题了。
协程和IO多路复用之间的合作,不仅保留了IO多路复用的高并发性能,还解放了业务逻辑的实现。
协程与IO多路复用结合的项目:
参考资料: