抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

IO多路复用

在高并发环境(如网络服务器)中同时处理多个请求的能力就显得尤为重要。很多人可能第一印象就会想到多线程,但是多线程需要进行很多的上下文切换,连接很多时上下文切换的代价就很高。因此采用单线程,但是单线程如何处理并发请求呢?

得益于DMA,多数据到来时直接内存访问,不会丢失数据,因此可以有后序内容

以写一个网络服务器为例。每个网络连接在都是以文件描述符(以下简称fd)的形式存在

select

    1. 准备文件描述符集合fds
      • 用一个数组存储已经创建的文件描述符,一个文件描述符可以看作是一个随机数
      • 找出最大的文件描述符max
    1. 初始哈(置零)一个字节数组(bitmap)rset来表示哪个文件描述符有数据到来
      • rset默认1024位
    1. 将rset拷贝到内核态,由内核判断fd是否有数据
      • 因为判断fd是要在内核态完成的,与其一个个切换然后查询,不然全部传入
      • 如果没有数据则内核会阻塞,直到有数据到来,将对应的rset置位然后返回
    1. 再根据rset判断文件描述符集合中是否有数据,读取有数据的描述符
  • 缺点

    • 字节数组大小有限
    • 每次rset都要清零
    • 拷贝到内核态任有一定开销
    • 解阻塞后仍需要遍历fds,O(n)复杂度

poll

工作原理与select类似,做了一些改进

1
2
3
4
5
struct pollfd{
int fd;
short events; // 表示要监听的事件
short revents; // 表示有无事件发生
}
    1. 准备文件描述符集合pollfds,用pollfd数组储存
    1. 同select传入内核检查,然后阻塞
      • 当有事件发生时revents置位,返回
    1. 检查revents,有事件时进行处理,然后置零
  • 改进

    • 用pollfd数组存储解决了大小问题
    • 让revents置零使得可重用,不用每次都全部置零

epoll

    1. 创建一个特殊结构epfd用于存储文件描述符集合
    1. 内核和用户会共享epfd的内存
    1. 当内核监听到事件发生时,对epfd中的元素进行重排,然后返回事件数
      • 因为没有事件发生时会阻塞,所以解阻塞时一定有事件发生,而且事件一定排在前头
    1. 根据事件数n,遍历前n的事件即可
  • 改进

    • 解决了内核态拷贝的开销
    • 解决了O(n)复杂度问题
  • 应用

    • redis
    • nginx

评论