背景
关于IO多路复用,可谓真是让人又爱又恨,爱的是他没有多线程那般的繁琐却能解决类似多线程的问题,恨的是初学时很难理解。
我看过网上很多人写过select函数的教程,但看过一遍之后大多都是走马观花,总是觉得少了什么。缺少了一点“烟火气”。
下面的这些内容是我经过自己的思考后得到的一些心得,随说没有那么多的专业名词,但我觉得绝对是相当“接地气”,如有错误指出还恳请斧正
IO多路复用
IO多路复用是干啥的?在搞懂这个问题之前我们先搞懂另外一个问题:阻塞的本质是什么?
阻塞的本质
现在想象你在下饭馆,由于服务员需要一定的时间上菜,在没有上菜的这段时间,你不能吃任何东西,这就是阻塞
。那么这种情况该如何放到计算机中进行解释呢?
很简单,你将进食的行为看作是CPU工作的行为就可以了。
也就是说,当我们要的数据暂时没有的时候,会进入一种“等待”状态,CPU会进入一种“空转”的状态。
当一个进程或线程等待某个条件(通常是某种资源)满足时,它会进入阻塞状态。在阻塞状态下,进程或线程不能继续执行,直到其等待的条件得到满足。
导致阻塞的情况
常见的导致阻塞的情况有下面三种:
- 等待I/O操作完成:例如,一个进程读取磁盘文件或从网络接收数据,这些操作通常需要一段时间才能完成,进程在等待这些操作完成时会被阻塞。
- 等待获取锁:例如,多个进程或线程竞争同一资源时,可能需要使用锁来同步,一个进程在等待获取锁时会被阻塞。
- 等待进程间通信:例如,一个进程在等待另一个进程发送消息或信号时会被阻塞。
当然,上面这三点中,只有第一条是我们这篇文章讨论的重点。
什么是IO多路复用
正常情况下,我们的一个进程,只能支持一个文件的IO操作,例如:
scanf
,这个函数大家都不陌生,它将数据从stdin
中读出,然后放进一个空间里面。而stdin
的本质是一个文件,对应着系统中的文件描述符号1
。
不管怎么说,它的本质就是在进行一个读文件的操作,对吧?
那IO多路复用是怎么回事儿呢?
还是拿下饭馆这件事来说,如果你是一个人去吃饭,那么等着上菜的时间自然是可以什么都不做的,但是如果你和朋友一起去吃饭,在上菜之前,你可以在等待的过程中和朋友聊天。在等待的过程中,可以做别的事情。
将这件事反映到CPU上之后,就可以看成,CPU跳出了“空转”的状态,执行别的事情。
实现原理
这件事情该怎么实现呢?
我们知道,不管是read
还是write
,他们都是依靠缓冲区来操作的。
- 对于读操作来说,当缓冲区中没有数据时会进入“阻塞状态”
- 对于写操作来说,当缓冲区中有数据时(说明还未被读出来),会进入“阻塞状态”
基于缓冲区阻塞的原理,我们思考以下过程:
是否可以让CPU跳出空装状态,去执行别的事情。例如,当我们要读数据时,我们不让CPU等待数据,而是定期检查缓冲区中是否存在数据可读,当有数据可读时,再读入,执行后续操作。
上述操作就是IO多路复用的原理
select函数的使用方式
在内核中,维护了一张表:文件描述符表大概长成这样:
他的实现结构是顺序表
在内核外,存在这样一种集合:
我们称这个结构叫做fd_set
,本质上是一种顺序表。
select是怎么实现上面的原理的呢?就是通过构建这两个表的联系。
在fd_set
中,每一个编号都与内核中的文件描述符表一一对应,当你在fd_set
中将对应编号位置的数字改为1的时候,通过执行select
会将fd_set
这个表拷贝到文件描述符表中,如下:
紧接着,内核会逐一检查文件描述符表,通过这个操作,去检查每个文件描述符对应的缓冲区(read write),如果存在可读或者是可写的文件(根据缓冲区类型而定)则返回到fd_set
中对应编号的标记。检查一遍之后,fd_set
中的内容会被彻底改变,也就是说,传入参数现在变成了返回参数。
在这个返回的fd_set
结构中,仍然被标记为1的位置编号所对应的文件描述符号就是可以读或者写的文件。此时,对他们进行读写操作一定不会被“阻塞”。
总结一下,select函数的作用就是监视一堆文件是否被可读或可写(当然,还有异常),返回可读以及可写文件的个数。
当然select
的最后一个参数是一个时间。这个时间的作用是用于规定监视时间的,具体规则如下,如果在时间之内,有文件描述符的变化则立即返回,如果规定时间内没有并超时,则返回0
,当然你也可以设置为NULL
,这样,select
就会一直监视,直到出现变化为止。
如果文章中有错误或存在疑问,欢迎指出和提问~~~
作者一个工作日之内必定回复!