IO多路复用就是服务端用来处理大量客户端同时连接的情况,select,poll,epoll都是IO多路复用的机制
何为IO多路复用?
IO多路复用就是一个线程或者一个进程同时监视多个文件描述符,一旦某个或某几个文件描述符准备就绪(读写就绪),就通知程序进行相应的读写操作。所以,这里的多路是指有多个网络连接,即多个客户端同时请求的情况,复用:复用同一个线程来监视所有网络连接,一旦某个或者某几个连接读写准备就绪,则通知应用程序进行读写
IO多路复用解决了什么问题?
没有IO多路复用机制时,有BIO、NIO两种实现方式,但有一些问题
accept
一个请求后,在recv或send调用阻塞时,将无法accept其他请求(必须等上一个请求处recv或send完),无法处理并发什么场景需要使用IO多路复用?
与多线程多进程相比,IO多路复用的最大优势就是复用一个线程或者一个进程,系统开销小,也不必对多线程多进程进行维护。
现在操作系统都采用虚拟存储器,对于32位操作系统而言,他的寻址空间就是4G(2的32次方),操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限,为了保护应用程序不直接操作内核,保证内核安全,操作系统将虚拟地址空间划分为两部分:
内核空间(内核态)
用户空间(用户态)
针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间
进程的上下文切换是在内核完成的,过程如下:
文件描述符就是一个用于描述指向文件的引用的抽象画概念
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表
。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
数据到达的路径:
等待数据和数据拷贝的过程中,这两个过程用户程序都是阻塞的
优点:
缺点:
非阻塞的recvfrom
系统调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error
。进程在返回之后,可以处理其他业务逻辑,然后以轮询的方式发起recvfrom
系统调用,直到数据准备好,再拷贝数据到进程,进行数据处理,但是数据拷贝的过程用户进程还是阻塞的,所以NIO最大的特点就是不断主动轮询内核,数据准备好了吗
优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。
缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。而且轮询也消耗CPU时间
用户程序进行aio_read
系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态程序继续执行其他业务逻辑,等到数据准备完成且数据拷贝完成后,内核向进程发送通知,这时候应用程序才开始处理IO读写,AIO中,数据准备和数据拷贝这两个过程都不会阻塞应用程序。
通知方式:
IO多路复用有两个特别的系统调用函数select,poll,epoll
,这三个函数可以为我们监听是否有socket数据准备好,只有有1个以上准备好就立马返回,用户进程在等待数据拷贝完成后就可以进行读写
当用户进程调用了select,那么整个进程会被block
,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回
。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。数据拷贝的过程,用户进程也是阻塞的,拷贝完成后,即可进行数据读写。
**多路复用的特点:**就是通过一个线程或者一个进程同时监视/等待多个IO文件描述符,只要有一个以上数据准备完成,即可返回。监视的方式分为:
但是千万别以为IO多路复用一定比BIO或者NIO优秀:
select , recvfrom
)而BIO只需要调用recvfrom
这一个系统调用,如果连接数并不是很多的话,可能延迟还会更大,如果一个连接占用的时间过长也不好,比如要读写的数据很大,就会造成后续已完成数据准备的请求无法及时读写,所以IO多路复用的优势在于处理多连接和短连接I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
select函数会监视文件描述符,调用select函数会阻塞,直到有描述符读就绪或者写就绪,或者超过指定的超时时间,函数返回,当select函数返回后,可以通过遍历fdset来找到准备就绪的描述符
select函数的缺点:
bitmap
这个数据结构来对监视的文件描述符做标记,这个bitmap
在linux中默认是1024,select函数在运行过程中将这个bitmap
拷贝到内核,让内核来判断哪个文件描述符准备就绪,所以,这个数据拷贝过程也会带来的一定的系统开销poll本质上和select没有区别,与select的区别的是他没有最大连接数的限制,原因是他是基于链表来存储的
poll还有一个特点是“水平触发”
,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
缺点:
从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket
。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态
,因此随着监视的描述符数量的增长,其效率也会线性下降。
epoll是select和poll的增强版本
epoll支持水平触发和边缘触发:
epoll还支持使用事件的就绪通知方式,通过epoll_ctl注册文件描述符,一旦FD准备就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
select,poll,epoll
监视内核数据准备,数据拷贝的过程仍然是阻塞的,适合于多连接,短连接redis的高性能的一个原因是redis采用网络IO多路复用的技术保证在多连接的时候,系统的高吞吐量
多路-指的是多个socket连接,复用-指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。