【面试八股总结】Linux系统下的I/O多路复用

news2024/9/21 7:41:04

参考资料 :小林Coding、阿秀、代码随想录

        I/O多路复用是⼀种在单个线程或进程中处理多个输入和输出操作的机制。它允许单个进程同时监视多个文件描述符(通常是套接字),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

        I/O多路复用允许在⼀个线程中处理多个I/O操作,避免了创建多个线程或进程的开销。

一、SELECT

        select是⼀个最古老的I/O多路复⽤机制,它可以监视多个⽂件描述符的可读、可写和错误状态,但是它的效率可能随着监视的文件描述符数量的增加而降低。

实现方式:

        select 将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝内核里,让内核来检查是否有事件产生,检查的方式就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。 

相关函数:

#include <sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, 
                fd_set* exceptfds, struct timeval* timeout);
- 参数:
    - nfds : 委托内核检测的最大文件描述符的值 + 1
    - readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
        - 一般检测读操作
        - 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区
        - 是一个传入传出参数
    - writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性
        - 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写)
    - exceptfds : 检测发生异常的文件描述符的集合
    - timeout : 设置的超时时间
            struct timeval {
            long tv_sec; /* seconds */
            long tv_usec; /* microseconds */
            };
            - NULL : 永久阻塞,直到检测到了文件描述符有变化
            - tv_sec = 0 tv_usec = 0, 不阻塞
            - tv_sec > 0 tv_usec > 0, 阻塞对应的时间
- 返回值 :
    - -1 : 失败
    - >0(n) : 检测的集合中有n个文件描述符发生了变化
// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);

// 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
int FD_ISSET(int fd, fd_set *set);

// 将参数文件描述符fd 对应的标志位,设置为1
void FD_SET(int fd, fd_set *set);

//  fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);

缺      点:

  1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  2. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  3. select支持的文件描述符数量太小了,默认是1024
  4. fds集合不能重用,每次都需要重置(因为内核会修改发生事件的fd)

二、POLL

     poll是select的⼀种改进,使用轮询方式来检查多个文件描述符的状态,避免了select中文件描述符数量有限的问题。但对于大量的文件描述符,poll的性能也可能变得不够⾼效。

改  进  点:

  1. 基于结构体数组存储要监视的文件描述符,文件描述符数量不受限制,可以处理任意数量的文件描述符;
  2. 结构体中使用revents作为是否发生事件标志,每次遍历只需要将revernts恢复为0,因此文件描述符集合可以重用。
#include <poll.h>
struct pollfd {
    int fd; /* 委托内核检测的文件描述符 */
    short events; /* 委托内核检测文件描述符的什么事件 */
    short revents; /* 文件描述符实际发生的事件 */
};

struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;

函数原型:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 参数:
        - fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
        - nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
        - timeout : 阻塞时长
            0 : 不阻塞
            -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
            >0 : 阻塞的时长
- 返回值:
        -1 : 失败
        >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
    

缺      点:

  1. 每次调用poll时,仍然需要将pollfd集合从用户态拷贝到内核态;
  2. 每次调用poll时,都需要在内核遍历传递进来的所有pollfd。

三、EPOLL

        EPOLL是Linux特有的I/O复用函数。epoll 使用⼀个事件驱动(event-driven)的方式来处理I/O操作,它只会返回就绪的文件描述符,而不是遍历整个文件描述符集合。

        epoll使用一组函数完成任务,而不是一个函数,并且epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,不需要像select和poll一样每次调用都需要重复传入文件描述符集合。但epoll需要一个额外的文件描述符,用于唯一标识内核中的事件表。

相关函数:

include <sys/epoll.h>
// 创建一个新的epoll实例。在内核中创建了一个数据,这个数据中有两个比较重要的数据,
// 一个是需要检测的文件描述符的信息(红黑树),
// 还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(双向链表)。
int epoll_create(int size);
    - 参数:
    size : 目前没有意义了。随便写一个数,必须大于0
    - 返回值:
        -1 : 失败
        > 0 : 操作epoll实例的文件描述符
// 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    - 参数:
        - epfd : epoll实例对应的文件描述符
        - op : 要进行什么操作
            EPOLL_CTL_ADD: 添加
            EPOLL_CTL_MOD: 修改
            EPOLL_CTL_DEL: 删除
        - fd : 要检测的文件描述符
        - event : 检测文件描述符什么事情
                struct epoll_event {
                    uint32_t events; /* Epoll events */
                    epoll_data_t data; /* User data variable */
                };

           常见的Epoll检测事件:    - EPOLLIN    - EPOLLOUT    - EPOLLERR
// 检测函数
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    - 参数:
        - epfd : epoll实例对应的文件描述符
        - events : 传出参数,保存了发送了变化的文件描述符的信息
        - maxevents : 第二个参数结构体数组的大小
        - timeout : 阻塞时间
            - 0 : 不阻塞
            - -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞
            - > 0 : 阻塞的时长(毫秒)
    - 返回值:
        - 成功,返回发送变化的文件描述符的个数 > 0
        - 失败 -1

LT和ET模式:

        epoll 支持两种事件触发模式,分别是边缘触发(edge-triggered,ET水平触发(level-triggered,LT

  • 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完
  • 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;

        ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

        ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

EPOLLONESHOT事件:

        即使使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中会引起一个问题,假设一个线程在读取完某个socket上的数据后开始处理该数据,而在数据处理过程中该socket又有新的数据可读(EPOLLIN再次被触发),此时另一个线程被唤醒来读取这些新的数据,于是就出现了两个线程同时操作一个socket的局面。

        我们期望一个socket连接在任何时候都只被一个线程处理,可以采用EPOLLONESHOT事件实现。对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非使用epoll_cnt函数重置改文件描述符上注册的EPOLLONESHOT事件。

SELECT、POLL和EPOLL区别:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1617120.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【精简改造版】大型多人在线游戏BrowserQuest服务器Golang框架解析(2)——服务端架构

1.架构选型 B/S架构&#xff1a;支持PC、平板、手机等多个平台 2.技术选型 &#xff08;1&#xff09;客户端web技术&#xff1a; HTML5 Canvas&#xff1a;支持基于2D平铺的图形引擎 Web workers&#xff1a;允许在不减慢主页UI的情况下初始化大型世界地图。 localStorag…

C++——类和对象练习(日期类)

日期类 1. 构造函数和析构函数2. 拷贝构造和赋值运算符重载3. 运算符重载3.1 日期的比较3.2 日期加减天数3.3 日期减日期3.4 流插入和流提取 4. 取地址和const取地址重载5. 完整代码Date.hDate.c 对日期类进行一个完善&#xff0c;可以帮助我们理解六个默认成员函数&#xff0c…

30V-STM32设计项目

30V-STM32设计 一、项目描述 (已验证) 基于STM32c8t6芯片设计的开发板&#xff0c;支持4-30V宽电压输入&#xff0c;串口模式自动下载功能&#xff0c;支持串口和STlink&#xff0c;方式下载程序 二、原理图介绍 电源电路采用了DCDCLDO电路&#xff0c;如果是外接DC头供电的话&…

机器学习之sklearn基础教程

目录 前言 一、安装 Sklearn 二、导入 Sklearn 三、加载数据集 四、划分数据集 五、数据预处理 六、选择模型并训练 七、模型评估 八、实验不同的模型 九、调整模型参数 十、实例&#xff1a;使用Sklearn库来进行鸢尾花&#xff08;Iris&#xff09;数据集的分类 #s…

微信小程序 讯飞录音 点击按钮录音内容转文字

<page-meta page-style"{{ showPolish ? overflow: hidden; : }}" /> <view class"wrap"> <view class"header-tab" style"justify-content: {{typeList.length > 2 ? start : center}}"><view class&quo…

加速大数据分析:Apache Kylin使用心得与最佳实践详解

Apache Kylin 是一个开源的分布式分析引擎&#xff0c;提供了Hadoop之上的SQL接口和多维分析&#xff08;OLAP&#xff09;能力以支持大规模数据。它擅长处理互联网级别的超大规模数据集&#xff0c;并能够进行亚秒级的查询响应时间。Kylin 的主要使用场景包括大数据分析、交互…

【基础算法】双指针

1.移动零 移动零 思路&#xff1a; 利用双指针算法 cur&#xff1a;从左往右扫描数组&#xff0c;遍历数组 dest&#xff1a;处理好的区间包括dest dest初始化为-1&#xff0c;因为刚开始dest前应该没有非零元素。 即将非零元素移到dest之前即可 class Solution { public…

BFS解决FloodFill算法:(Leetcode:733. 图像渲染)

题目链接&#xff1a;733. 图像渲染 - 力扣&#xff08;LeetCode&#xff09; 使用广度优先遍历算法解决该问题&#xff1a; 从初始位置开始搜索&#xff0c;初始位置符合条件就入栈&#xff0c;并修改初始位置值。初始位置出栈。 再从初始位置开始广度优先搜索&#xff08;…

【机器学习300问】78、都有哪些神经网络的初始化参数方法?

在训练神经网络时&#xff0c;权重初始化是确保良好收敛的关键步骤之一。不合适的初始化方法可能会导致梯度消失或爆炸&#xff0c;特别是在深层网络中。那么都有哪些神经网络的初始化参数方法呢&#xff1f;选择它这些方法的原则是什么&#xff1f; 一、常用神经网络初始化参…

Kubernetes(k8s)的概念以及使用

k8s的概念&#xff1a; K8s是指Kubernetes&#xff0c;是一个开源的容器编排和管理平台。它最初由Google开发&#xff0c;并于2014年将其开源。Kubernetes旨在简化容器化应用程序的部署、扩展和管理。 Kubernetes提供了一种可靠且可扩展的平台&#xff0c;用于管理容器化应用…

怎样才能迅速了解一个产品的业务流程?

很多小伙伴经常问我&#xff0c;刚进入一家新的企业&#xff0c;想要快速了解产品的业务流程&#xff0c;不知从何下手。主要是因为&#xff0c;有的企业根本没有文档可看&#xff1b;还有的企业有文档&#xff0c;但是记录的比较凌乱&#xff0c;想要从中找出点头绪来&#xf…

【Python-装饰器】

Python-装饰器 ■ 简介■ 装饰器的一般写法&#xff08;闭包写法&#xff09;■ 装饰器的语法 (outer写法) ■ 简介 装饰器其实是一种闭包&#xff0c; 功能就是在不破坏目标函数原有的代码和功能的前提下为目标函数增加新功能。 ■ 装饰器的一般写法&#xff08;闭包写法&am…

2024年前端技术发展趋势

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

程客有话说05 | 吕时有:在GIS行业深耕13年,做梦做出来了数学竞赛题,这是让我最开心的事

《程客有话说》是我们最新推出的一个访谈栏目&#xff0c;邀请了一些国内外有趣的程序员来分享他们的经验、观点与成长故事&#xff0c;我们尝试建立一个程序员交流与学习的平台&#xff0c;也欢迎大家推荐朋友或自己来参加我们的节目&#xff0c;一起加油。 本期我们邀请的程…

使用Docker搭建本地Nexus私有仓库

0-1开始Java语言编程之路 一、Ubuntu下Java语言环境搭建 二、Ubuntu下Docker环境安装 三、使用Docker搭建本地Nexus Maven私有仓库 四、Ubuntu下使用VisualStudioCode进行Java开发 你需要Nexus Java应用编译构建的一种主流方式就是通过Maven, Maven可以很方便的管理Java应用的…

网盘兼职真的能月入过万吗?你适合做哪种网盘分享牛?

1. 分享大容量文件&#xff1a; 提供常见软件安装包、系统镜像、游戏资源等常用的大容量文件&#xff0c;以满足用户的需求。 创建分类目录&#xff0c;便于用户浏览和查找所需文件。 编写详细的文件描述&#xff0c;包括文件版本、适用系统、安装方法等信息&#xff0c;帮助用…

Promise.all 的方法还没执行完就执行了.then

碰见一个问题&#xff0c;接盘了一个有问题的页面修改。 改变日期后 查询很多数据再去重新加载页面上的数据显示相关的组件。 问题就来了。 加载异常捏…… 最后我一通查&#xff1a; 重点来了 是因为这个Promise.all(数组)&#xff0c;里边这个数组的问题。现在是在数据中…

XYCTF 部分wp及学习记录

1.ezmd5 根据题目提示 我们知道应该是要上传两张md5值相同的图片 根据原文链接&#xff1a;cryptanalysis - Are there two known strings which have the same MD5 hash value? - Cryptography Stack Exchange 把保存下来的图片上传一下 得到flag 2.ezhttp 根据原文链接&…

STM32H7的LCD控制学习和应用

STM32H7的LCD控制 LTDC基础硬件框图LTDC时钟源选择LTDC的时序配置LTDC背景层、图层1、图层2和Alpha混合LTDC的水平消隐和垂直消隐LCD的DE同步模式和HV同步模式的区别区分FPS帧率和刷新率避免LTDC刷新撕裂感的解决方法 驱动示例分配栈的大小MPU和Cache配置初始化SDRAM初始化LCD应…

鸿蒙 harmonyos 线程 并发 总结 async promise Taskpool woker(三)多线程并发 Worker

Worker Worker是与主线程并行的独立线程。创建Worker的线程称之为宿主线程&#xff0c;Worker自身的线程称之为Worker线程。创建Worker传入的url文件在Worker线程中执行&#xff0c;可以处理耗时操作但不可以直接操作UI。 Worker主要作用是为应用程序提供一个多线程的运行环境…