你还好意思说精通MySQL查询优化?
多路复用器 尽管上面的单线程NIO服务器模型比BIO的优良许多,但是仍然有一个大问题。在客户端与服务器建立连接后,后续会进行一系列的读写操作。虽然这些读写操作是非阻塞的,但是每调一次读写操作在操作系统层面都要进行一次用户态和内核态的切换,这个也是一项巨大的开销(读写等系统调用都是在内核态完成的)。 在上面的代码中每次循环遍历都进行读写操作,我们以读操作为例:大部分读操作都是在数据没有准备好的情况下进行读的,相当于执行了一次空操作。我们要想办法避免这种无效的读取操作,避免内核态和用户态之间的频繁切换。 补充:客户端与服务器两端都是通过socket进行连接的,socket在linux操作系统中有对应的文件描述符,我们的读写操作都是以该文件描述符为单位进行操作的。 为了避免上述的无效读写,我们得想办法得知当前的文件描述符是否可读可写。如果逐个文件描述符去询问,那么效率就和直接进行读写操作差不多了,我们希望有一种方法能够一次性得知哪些文件描述符可读,哪些文件描述符可写,这,就操作系统后来发展出的多路复用器。 也就是说,多路复用器的核心功能就是告诉我们哪些文件描述符可读,哪些文件描述符可写。而多路复用器也分为几种,他们也经历了一个演化的过程。最初的多路复用器是select模型,它的模式是这样的:程序端每次把文件描述符集合交给select的系统调用,select遍历每个文件描述符后返回那些可以操作的文件描述符,然后程序对可以操作的文件描述符进行读写。 它的弊端是,一次传输的文件描述符集合有限,只能给出1024个文件描述符,poll在此基础上进行了改进,没有了文件描述符数量的限制。 但是select和poll在性能上还可以优化,它们共同的弊端在于: 它们需要在内核中对所有传入的文件描述符进行遍历,这也是一项比较耗时的操作 (这点是否存在优化空间有待考证)每次要把文件描述符从用户态的内存搬运到内核态的内存,遍历完成后再搬回去,这个来回复制也是一项耗时的操纵。 后来操作系统加入了epoll这个多路复用器,彻底解决了这个问题: epoll多路复用器的模型是这样的: 为了在发起系统调用的时候不遍历所有的文件描述符,epoll的优化在于:当数据到达网卡的时候,会触发中断,正常情况下cpu会把相应的数据复制到内存中,和相关的文件描述符进行绑定。epoll在这个基础之上做了延伸,epoll首先是在内核中维护了一个红黑树,以及一些链表结构,当数据到达网卡拷贝到内存时会把相应的文件描述符从红黑树中拷贝到链表中,这样链表存储的就是已经有数据到达的文件描述符,这样当程序调用epoll_wait的时候就能直接把能读的文件描述符返回给应用程序。 除了epoll_wait之外,epoll还有两个系统调用,分别是epoll_create和epoll_ctl,分别用于初始化epoll和把文件描述符添加到红黑树中。 以上就是多路复用器与常见io模型的关系了,网上常常有文章把多路复用器说成是nio的一部分,我觉得也是合理的,因为在具体编程的时候两个概念往往会融为一体。 后续
其实Java已经为我们把多路复用器用Selector类给封装起来了,我们完全可以基于Selector进行NIO服务器开发。但是我们自己写nio服务器可能不够严谨,Java届有一款优秀nio框架,名叫Netty,这部分内容我们留到下一次再讲啦。 (编辑:淮安站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |