几个概念
- 应用程序: 一般指需要处理并发请求的服务端
- 文件句柄:一般指socket流
- 用户态:用户进程所运行的空间,个人理解是当前进程的运行环境和使用的系统资源
- 内核态:系统内核,cpu调度,内存管理,用户态通过系统调用来使用内核中的资源
- 监听者:select、epoll叫做监听者
IO多路复用需要解决什么问题:
1,节省cpu资源
2,提高并发性能
要完成对IO多路复用需要做如下几个事情:
1,用户态怎么将文件句柄传递到内核态
2,内核态怎么判断IO流可读可写
3,内核怎么通知监控者IO流有可读可写事件发生
4,监控者如何找到可读可写的IO流并传递给用户态应用程序
5,继续循环时监控者怎么重复上述步骤
select
阻塞当前线程,以轮询的方式对IO流进行监听,如果存在可读、可写事件,通知当前被阻塞的线程,但是它会轮询当前所有的IO流,
select方法接收三个参数,可读列表,可写列表,异常列表,返回三个list
select方法会将客户端socket请求重用户态copy到内核态,在内核中维护一个数组,并不停的轮训这个数组
当轮训到数组元素中socket发生变化,可读事件(客户端连接,客户端发送数据),可写事件(服务端返回数据),就将可读或可写的数据返回,并继续轮询
当前进程获取到可读/可写列表后,判断是否客户端类型是否数据当前socket类型,并且处理请求,处理完请求后将数据socket放入可写列表中,再次发送给select函数
并不断重复这个过程
epoll:
1,内核态执行系统调用 epoll_create在内核创建专属于epoll的高速cache区,并在改缓冲区建立红黑树和就绪链表,用户态传入的文件句柄将被传入到红黑树中
引用
多路复用适用于需要保持大量闲置(区别于计算密集型)长连接的业务场景,例如聊天室。这样的好处是能够避免不断的创建新线程,导致系统资源浪费。需要注意,多路复用本质上是复用单线程的,回调函数的执行必然是有可能长时间阻塞的,所以如果涉及到耗时的计算密集型任务,则会大大降低系统处理其它连接的响应速度。
线程池则适合短连接并发的情况,比如普通的web业务系统,Tomcat的Servlet容器默认选择就是线程池(虽然3.0后支持异步,但一般情况下不常使用)。由于处理短连接的线程很快会退出,因此能够充分发挥线程池复用线程的好处。
当然,多路复用和线程池可以结合起来使用,效果也许更好,但代码复杂度也会相应提高,需要更好的设计。建议根据业务场景选择相应的技术,避免过早优化。
一点补充:很多人不知道协程该归于哪个技术范畴。协程除了在用户态通过栈切换实现控制流的切换以外,还通常将多路复用和线程池结合起来。比如go语言内置的协程就是在多线程的基础上实现了一套调度策略,调度策略的实现建立在操作系统内核提供的IO多路复用技术之上,同时go语言参考计算机硬件情况自动将协程绑定在若干个系统线程之上,从而实现资源的高效率利用。
参考文章: