IO多路复用,select、poll、epoll区别

2024-09-04 07:48

本文主要是介绍IO多路复用,select、poll、epoll区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • IO多路复用是一种同步IO模型,一个线程监听多个IO事件,当有IO事件就绪时,就会通知线程去执行相应的读写操作,没有就绪事件时,就会阻塞交出cpu。多路是指网络链接,复用指的是复用同一线程。

  • select

    • fd_set数据结构定义如下,可以看出fd_set是一个整型数组,用于保存socket文件描述符
    typedef long int __fd_mask;/* fd_set for select and pselect.  */
    typedef struct{
    #ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
    # define __FDS_BITS(set) ((set)->fds_bits)
    #else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
    # define __FDS_BITS(set) ((set)->__fds_bits)
    #endif} fd_set;
    • 执行过程

      流程:

      1. 用户线程调用select,将fd_set从用户空间拷贝到内核空间
      2. 内核在内核空间对fd_set遍历一遍,检查是否有就绪的socket描述符,如果没有的话,就会进入休眠,直到有就绪的socket描述符
      3. 内核返回select的结果给用户线程,即就绪的文件描述符数量
      4. 用户拿到就绪文件描述符数量后,再次对fd_set进行遍历,找出就绪的文件描述符
      5. 用户线程对就绪的文件描述符进行读写操作
    • 优点

      1. 所有平台都支持,良好的跨平台性
    • 缺点

      1. 每次调用select,都需要将fd_set从用户空间拷贝到内核空间,当fd很多时,这个开销很大
      2. 最大连接数(支持的最大文件描述符数量)有限制,一般为1024
      3. 每次有活跃的socket描述符时,都需要遍历一次fd_set,造成大量的时间开销,时间复杂度是O(n)
      4. 将fd_set从用户空间拷贝到内核空间,内核空间也需要对fd_set遍历一遍
  • poll

    • 数据结构

      数据结构定义如下,链表存储

      /* Data structure describing a polling request.  */
      struct pollfd{int fd;			/* File descriptor to poll.  */short int events;		/* Types of events poller cares about.  */short int revents;		/* Types of events that actually occurred.  */};
    • 与select的异同点

      相同点:

      1. 内核线程都需要遍历文件描述符,并且当内核返回就绪的文件描述符数量后,还需要遍历一次找出就绪的文件描述符
      2. 需要将文件描述符数组或链表从用户空间拷贝到内核空间
      3. 性能开销会随文件描述符的数量而线性增大

      不同点:

      1. select存储的数据结构是文件描述符数组,poll采用链表
      2. select有最大连接数限制,poll没有最大限制,因为poll采用链表存储
    • 执行过程(基本与select类似)

      1. 用户线程调用poll系统调用,并将文件描述符链表拷贝到内核空间
      2. 内核对文件描述符遍历一遍,如果没有就绪的描述符,则内核开始休眠,直到有就绪的文件描述符
      3. 返回给用户线程就绪的文件描述符数量
      4. 用户线程再遍历一次文件描述符链表,找出就绪的文件描述符
      5. 用户线程对就绪的文件描述符进行读写操作
  • epoll

    • 核心代码

      #include <sys/epoll.h>// 数据结构
      // 每一个epoll对象都有一个独立的eventpoll结构体
      // 红黑树用于存放通过epoll_ctl方法向epoll对象中添加进来的事件
      // epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可
      struct eventpoll {.../*红黑树的根节点,这颗树存储着所有添加到epoll中的需要监控的事件*/struct rb_root  rbr;/*双链表存储所有就绪的文件描述符*/struct list_head rdlist;...
      };// API
      int epoll_create(int size); // 内核中间加一个 eventpoll 对象,把所有需要监听的 socket 都放到 eventpoll 对象中
      int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epoll_ctl 负责把 socket 增加、删除到内核红黑树
      int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);// epoll_wait 检测双链表中是否有就绪的文件描述符,如果有,则返回

      核心点:

      1. epoll_create创建eventpoll对象(红黑树,双链表)
      2. 一棵红黑树,存储监听的所有文件描述符,并且通过epoll_ctl将文件描述符添加、删除到红黑树
      3. 一个双链表,存储就绪的文件描述符列表,epoll_wait调用时,检测此链表中是否有数据,有的话直接返回
      4. 所有添加到eventpoll中的事件都与设备驱动程序建立回调关系
    • 缺点

      1. 只能工作在linux下
    • 优点

      1. 时间复杂度为O(1),当有事件就绪时,epoll_wait只需要检测就绪链表中有没有数据,如果有的话就直接返回
      2. 不需要从用户空间到内核空间频繁拷贝文件描述符集合,使用了内存映射(mmap)技术
      3. 当有就绪事件发生时采用回调的形式通知用户线程
  • select、poll、epoll的区别

    selectpollepoll
    底层数据结构数组存储文件描述符链表存储文件描述符红黑树存储监控的文件描述符,双链表存储就绪的文件描述符
    如何从fd数据中获取就绪的fd遍历fd_set遍历链表回调
    时间复杂度获得就绪的文件描述符需要遍历fd数组,O(n)获得就绪的文件描述符需要遍历fd链表,O(n)当有就绪事件时,系统注册的回调函数就会被调用,将就绪的fd放入到就绪链表中。O(1)
    FD数据拷贝每次调用select,需要将fd数据从用户空间拷贝到内核空间每次调用poll,需要将fd数据从用户空间拷贝到内核空间使用内存映射(mmap),不需要从用户空间频繁拷贝fd数据到内核空间
    最大连接数有限制,一般为1024无限制无限制
  • epoll 的水平触发(LT)和边缘触发(ET)的区别

    LT模式:只要文件描述符还有数据可读,每次epoll_wait就会返回它的事件(只要有数据就触发

    ET模式:只有数据流到来的时候才触发,不管缓冲区是否还有数据(只有数据流到来才会触发

  • IO多路复用应用场景

    1. 连接数较少并且都很活跃,用select和poll效率更高
    2. 连接数较多并且都不很活跃,使用epoll效率更高

这篇关于IO多路复用,select、poll、epoll区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1135421

相关文章

JAVA覆盖和重写的区别及说明

《JAVA覆盖和重写的区别及说明》非静态方法的覆盖即重写,具有多态性;静态方法无法被覆盖,但可被重写(仅通过类名调用),二者区别在于绑定时机与引用类型关联性... 目录Java覆盖和重写的区别经常听到两种话认真读完上面两份代码JAVA覆盖和重写的区别经常听到两种话1.覆盖=重写。2.静态方法可andro

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

MyBatis中$与#的区别解析

《MyBatis中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

Before和BeforeClass的区别及说明

《Before和BeforeClass的区别及说明》:本文主要介绍Before和BeforeClass的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Before和BeforeClass的区别一个简单的例子当运行这个测试类时总结Before和Befor