百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

超详细的I/O多路复用概念、常用I/O模型、系统调用等介绍

ztj100 2024-11-09 15:19 13 浏览 0 评论

概述

当我们要编写一个echo服务器程序的时候,需要对用户从标准输入键入的交互命令做出响应。在这种情况下,服务器必须响应两个相互独立的I/O事件:1)网络客户端发起网络连接请求,2)用户在键盘上键入命令行。我们先等待哪个事件呢?没有哪个选择是理想的。如果在acceptor中等待一个连接请求,我们就不能响应输入的命令。类似地,如果在read中等待一个输入命令,我们就不能响应任何连接请求。针对这种困境的一个解决办法就是I/O多路复用技术。基本思路就是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。 --《UNIX网络编程》

mysql线程池,就是I/O多路复用的体现。

参考:https://blog.csdn.net/wangxindong11/article/details/78591308

一、I/O多路复用概述

I/O多路复用,I/O就是指的我们网络I/O,多路指多个TCP连接(或多个Channel),复用指复用一个或少量线程。串起来理解就是很多个网络I/O复用一个或少量的线程来处理这些连接。

多路复用的本质是同步非阻塞I/O,多路复用的优势并不是单个连接处理的更快,而是在于能处理更多的连接。

I/O编程过程中,需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。

I/O多路复用技术通过把多个I/O的阻塞复用到同一个select阻塞上,一个进程监视多个描述符,一旦某个描述符就位, 能够通知程序进行读写操作。因为多路复用本质上是同步I/O,都需要应用程序在读写事件就绪后自己负责读写。

最大的优势是系统开销小,不需要创建和维护额外线程或进程。

  • 应用场景
  • 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字
  • 需要同时处理多种网络协议的套接字
  • 一个服务器处理多个服务或协议

目前支持多路复用的系统调用有select, poll, epoll。


二、几种常用I/O模型

BIO

阻塞同步I/O模型,服务器需要监听端口号,客户端通过IP和端口与服务器简历TCP连接,以同步阻塞的方式传输数据。服务端设计一般都是 客户端-线程模型,新来一个客户端连接请求,就新建一个线程处理连接和数据传输

当客户端连接较多时就会大大消耗服务器的资源,线程数量可能超过最大承受量

伪异步I/O

与BIO类似,只是将客户端-线程的模式换成了线程池,可以灵活设置线程池的大小。但这只是对BIO的一种优化手段,并没有解决线程连接的阻塞问题。

NIO

同步非阻塞I/O模型,利用selector多路复用器轮询为每一个用户创建连接,这样就不用阻塞用户线程,也不用每个线程忙等待。只使用一个线程轮询I/O事件,比较适合高并发,高负载的网络应用,充分利用系统资源快速处理请求返回响应消息,是和连接较多连接时间I/O任务较短

AIO

异步非阻塞,需要操作系统内核线程支持,一个用户线程发起一个请求后就可以继续执行,内核线程执行完系统调用后会根据回调函数完成处理工作。比较适合较多I/O任务较长的场景。


三、select

监视多个文件句柄的状态变化,程序会阻塞在select处等待,直到有文件描述符就绪或超时。

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

可以监听三类文件描述符,writefds(写状态), readfds(读状态), exceptfds(异常状态)。

我们在select函数中告诉内核需要监听的不同状态的文件描述符以及能接受的超时时间,函数会返回所有状态下就绪的描述符的个数,并且可以通过遍历fdset,来找到就绪的描述符。

缺陷

  • 每次调用select,都需要把待监控的fd集合从用户态拷贝到内核态,当fd很大时,开销很大。
  • 每次调用select,都需要轮询一遍所有的fd,查看就绪状态。
  • select支持的最大文件描述符数量有限,默认是1024

四、poll

与select轮询所有待监听的描述符机制类似,但poll使用pollfd结构表示要监听的描述符。

int poll(struct pollfd *fds, nfds_t nfds, int timeout)
 
struct pollfd
{
 short events;
 short revents;
};

pollfd结构包括了events(要监听的事件)和revents(实际发生的事件)。而且也需要在函数返回后遍历pollfd来获取就绪的描述符。

相对于select,poll已不存在最大文件描述符限制。


五、epoll

epoll针对以上select和poll的主要缺点做出了改进,

主要包括三个主要函数,epoll_create, epoll_ctl, epoll_wait。

  • epoll_create:创建epoll句柄,会占用一个fd值,使用完成以后,要关闭。

int epoll_create(int size)

  • epoll_ctl:提前注册好要监听的事件类型,监听事件(文件可写,可读,挂断,错误)。不用每次都去轮询一遍注册的fd,而只是通过epoll_ctl把所有fd拷贝进内核一次,并为每一个fd指定一个回调函数。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

当就绪,会调用回调函数,把就绪的文件描述符和事件加入一个就绪链表,并拷贝到用户空间内存,应用程序不用亲自从内核拷贝。类似于在信号中注册所有的发送者和接收者,或者Task中注册所有任务的handler。

  • epoll_wait:监听epoll_ctl中注册的文件描述符和事件,在就绪链表中查看有没有就绪的fd,不用去遍历所有fd。
  • 相当于直接去遍历结果集合,而且百分百命中,不用每次都去重新查找所有的fd,用户索引文件的事件复杂度为O(1)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

六、select & poll & epoll比较

  1. 每次调用select都需要把所有要监听的文件描述符拷贝到内核空间一次,fd很大时开销会很大。epoll会在epoll_ctl()中注册,只需要将所有的fd拷贝到内核事件表一次,不用再每次epoll_wait()时重复拷贝
  2. 每次select需要在内核中遍历所有监听的fd,直到设备就绪;epoll通过epoll_ctl注册回调函数,也需要不断调用epoll_wait轮询就绪链表,当fd或者事件就绪时,会调用回调函数,将就绪结果加入到就绪链表。
  3. select能监听的文件描述符数量有限,默认是1024;epoll能支持的fd数量是最大可以打开文件的数目,具体数目可以在/proc/sys/fs/file-max查看
  4. select, poll在函数返回后需要查看所有监听的fd,看哪些就绪,而epoll只返回就绪的描述符,所以应用程序只需要就绪fd的命中率是百分百。

表面上看epoll的性能最好,但是在连接数少并且链接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

select效率低是一位每次都需要轮询,但效率低也是相对的,也可通过良好的设计改善


七、阻塞、非阻塞

这张图可以看出阻塞式I/O、非阻塞式I/O、I/O复用、信号驱动式I/O他们的第二阶段都相同,也就是都会阻塞到recvfrom调用上面就是图中“发起”的动作。异步式I/O两个阶段都要处理。这里我们重点对比阻塞式I/O(也就是我们常说的传统的BIO)和I/O复用之间的区别。

阻塞式I/O和I/O复用,两个阶段都阻塞,那区别在哪里呢?

虽然第一阶段都是阻塞,但是阻塞式I/O如果要接收更多的连接,就必须创建更多的线程。I/O复用模式下在第一个阶段大量的连接统统都可以过来直接注册到Selector复用器上面,同时只要单个或者少量的线程来循环处理这些连接事件就可以了,一旦达到“就绪”的条件,就可以立即执行真正的I/O操作。这就是I/O复用与传统的阻塞式I/O最大的不同。也正是I/O复用的精髓所在。

从应用进程的角度去理解始终是阻塞的,等待数据和将数据复制到用户进程这两个阶段都是阻塞的。这一点我们从应用程序是可以清楚的得知,比如我们调用一个以I/O复用为基础的NIO应用服务。调用端是一直阻塞等待返回结果的。

从内核的角度等待Selector上面的网络事件就绪,是阻塞的,如果没有任何一个网络事件就绪则一直等待直到有一个或者多个网络事件就绪。但是从内核的角度考虑,有一点是不阻塞的,就是复制数据,因为内核不用等待,当有就绪条件满足的时候,它直接复制,其余时间在处理别的就绪的条件。这也是大家一直说的非阻塞I/O。实际上是就是指的这个地方的非阻塞。


总结

我们通常说的NIO大多数场景下都是基于I/O复用技术的NIO,比如jdk中的NIO,当然Tomcat8以后的NIO也是指的基于I/O复用的NIO。注意,使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。如果放到线上环境,网络情况在有时候并不稳定的情况下,这种基于I/O复用技术的NIO的优势就是传统BIO不可同比的了。那么使用select的优势在于我们可以等到网络事件就绪,那么用少量的线程去轮询Selector上面注册的事件,不就绪的不处理,就绪的拿出来立即执行真正的I/O操作。这样我们就能够用极少量的线程去HOLD住大量的连接。

后面会分享更多devops和DBA方面的内容,感兴趣的朋友可以关注下~

相关推荐

Vue 技术栈(全家桶)(vue technology)

Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...

vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)

前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...

vue3 组件初始化流程(vue组件初始化顺序)

学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...

vue3优雅的设置element-plus的table自动滚动到底部

场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...

Vue3为什么推荐使用ref而不是reactive

为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...

9、echarts 在 vue 中怎么引用?(必会)

首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...

无所不能,将 Vue 渲染到嵌入式液晶屏

该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...

vue-element-admin 增删改查(五)(vue-element-admin怎么用)

此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...

最全的 Vue 面试题+详解答案(vue面试题知识点大全)

前言本文整理了...

基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时

今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...

不来看看这些 VUE 的生命周期钩子函数?| 原力计划

作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...

Vue3.5正式上线,父传子props用法更丝滑简洁

前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...

Vue 3 生命周期完整指南(vue生命周期及使用)

Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...

救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘

前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...

怎么在 vue 中使用 form 清除校验状态?

在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...

取消回复欢迎 发表评论: