阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:
阻塞:指IO操作彻底完成后才返回到用户空间
非阻塞:指IO操作被调用后立即返回给用户一个状态,不需要等到IO操作彻底完成。
-
阻塞IO模型
在linux中,默认情况下所有的socket都是阻塞的,一个典型的读操作流程如下:
image.png
多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型会遇到瓶颈,可以用非阻塞模型来尝试解决问题。
-
非阻塞IO模型
在Linux下,可以通过设置socket使IO变为非阻塞状态。当对一个非阻塞的socket进行read操作时,流程如下:
image.png
非阻塞式IO中,用户进程其实需要不断主动询问kernel数据是否准备好,非阻塞的接口相对阻塞型接口的显著差异在于被调用之后立即返回。
-
多路IO复用模型
多路IO复用,有时也称为事件驱动IO。它的基本原理是有个函数会不断地轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程,多路IO模型的流程如图:
image.png -
异步IO模型
用户进程发起read操作后,立刻开始去做别的事情;另一方面,从内核角度,当它收到一个异步的read请求操作后,首先会立刻返回,所有不会对用户进程有任何阻塞。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存中,当这一切完成之后,内核会给用户进程发送一个信号,返回read操作已完成的信息。
image.png -
总结
image.png
select
使用select完成非阻塞方式工作的流程,能监视需要被监视的文件描述符的变化情况----读、写或异常。
- select函数原型
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
结构体fd_set是一个存放文件描述符的集合,也就是文件。
结构体timeval用来代表时间值,有两个成员,一个是秒数,一个是毫秒数。
readfds指向fd_set结构的指针,这个集合中应该包括文件描述符。关心是否可以从这些文件中读取数据,若可读,select返回一个大于0的值,表示有文件可读。若没有可读文件,则根据timeout参数再判断是否超时;若超时返回0。
writefds指向fd_set结构的指针,这个集合中应该包括文件描述符。关心是否可以向这些文件中写入数据,若可读,select返回一个大于0的值,表示有文件可写。若没有可写文件,则根据timeout参数再判断是否超时;若超时返回0。
timeout三种状态:1) 若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到文件描述符集合变化为止。2) 时间值设为0,变成非阻塞函数,会立刻返回继续执行,文件无变化返回0,有变化返回一个正值。3) timeout值大于0,就是等待超时时间
返回值准备就绪的描述符数,若超时则返回0,若出错返回-1。
poll
poll函数也可用于执行多路复用IO,
select、poll和epoll的区别
多路IO复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作。select、poll和epoll本质上都是同步IO,需要在读写事件就绪后自己负责进行读写,即是阻塞的。而异步IO无须自己负责读写,异步IO的实现会负责把数据从内核拷贝到用户空间。
- poll()比select()高级的原因
1,poll()在应付大数目的文件描述符时速度更快
2,poll()不要求开发者计算最大文件描述符时进行+1操作
3,select()监控的文件描述符数目是固定的,相对较少。poll()函数可创建特定大小数组保存监控的描述符,而不受文件描述符大小影响。
4,select()所监控的fd_set在select()返回之后会变化,所以下一次进入select()之前需要重新初始化需要监控的fd_set,poll()函数将监控的输入和输出事件分开,允许被监控文件数组被复用而不需要重新初始化。
5,每次超时之后下次进入select()之前都需要重新设置超时参数 - select()优点
1,可移植性好,对于超时值提供更好的精度 - epoll优点
1,支持一个进程打开大数目的socket描述符
2,IO效率不随FD数目增加而线性下降
因为在内核中实现epoll是根据每个fd上面的callback函数实现的。
3,使用mmap加速内核与用户空间的消息传递
epoll通过内核与用户空间mmap处于同一块内存实现的,而poll将用户传入的pollfd数组拷贝到内核空间,事件发生后,poll将获得的数据传送到用户空间并执行内存和剥离等待队列等工作。
网友评论