io多路复用技术对比:select vs epoll

我觉得要理解一个概念,往往需要把几个相近的概念拿到一起谈,然后对比各种异同,这样才能更好的理解,并且一通百通。

先谈谈select,首先select每次调用前需要进行FD_SET 关注多少描述符就需要调用多少次FD_SET,所以这个是o(n)的操作;其次每次select需要把描述符集合从用户空间拷贝到内核空间;再次在内核中查询的时候,select查询又是o(n)的查询,每次需遍历所有的关注的描述符查询是否满足条件(可读或者可写),然后返回给用户空间;最后select的描述符集合实现是数组,所以大小有一定的限制。

epoll对fd的管理采用红黑树,只有当fd真的增删前才会调用epoll_ctl(对比select,epoll而不是每次epoll_wait前需要添加所有的fd,也不会每次epoll_wait前有所有描述符数据从用户空间到内核空间的拷贝);其次红黑树增删都是o(logN)的操作,比较高效。最后当描述符准备好时,调用epoll_wait时,只需要返回满足条件的描述符链表,这个本身是o(1)的操作。最后epoll本身也是线程安全的,采用红黑树时,不论增删,复杂度都是0(logN),由于红黑树的性质,增最多2次旋转,删最多3次旋转,其余都是染色的过程,所以就算多线程访问锁的粒度也比较小,由于染色操作本身不影响查询,只需旋转时lock即可。

epoll工作有2种模式,水平触发和边缘触发,水平触发关注的事件是socket层缓冲的可读或者可写字节数大于1即添加到返回链表中,边缘触发当其仅当socket层需要的缓冲从不可读到可读,或者从不可写到可写才会添加到返回链表。所以水平模式下,当返回链表中描述符返回给用户的空间后,再次epoll_wait会在次检查之前的链表是否真没有关注的事件了,否则再次加入链表中,这个相对来说就比较低效了。在写的场景下epoll需要关注的事件是EPOLLOUT,如果使用水平触发,每次写入完毕后,需要使用EPOLL_CTL_MOD关闭对EPOLLOUT事件的关注,这个也是相对边缘触发模式下的开销,所以当写入越频繁,尤其在写入一个大的数据包的时候,由于协议窗口大小的限制无法一次写完,这时性能差异就能体现出来。

还有一个概念不论epoll还是select都没有进行io操作,本质都不算同步或者异步io的概念,获取到关注的描述符信息后还要再次进行io操作,比如read/write。read/wirte本身还有一个数据从内核空间和用户空间的转换的开销。

你可能感兴趣的:(io多路复用技术对比:select vs epoll)