简单介绍IO多路复用

IO多路复用 (简单介绍)

个人博客:kana.chat:90

那么,什么是IO呢?

首先如果要介绍到关于IO,肯定要介绍操作系统内核↓

关于内核(kernel)的介绍:

  按下电源键后,bios第一个读到内存的程序就是内核(kernel)。在内存中属于kernel的那部分空间可以当做是内核空间(内核态),而内存内其他空间就是用户空间(用户态),用户无权直接访问内核态,这样做也是为了保护系统安全。

如下图(右为用户态,kernel被装载在内存中),硬件的访问都需要内核来管理:

kernel装载

    内核使用系统调用(system call)来调用硬件,应用程序调用封装后的api来执行(例如java中的JNI方法),调用api需要用户态切换到内核态,拥有一定的开销。
而用户态到内核态的切换依赖于系统中断,程序的调用中断属于软中断 ,而硬件设备的电信号属于硬中断(鼠标的移动、键盘指令的输入)。

从硬件底层来讲轮询是靠cpu的晶振来完成的,每一次振动都是一次中断。例如readline每一次调用是阻塞的,内核读取到磁盘数据后拷贝到用户空间让其读取。这个时候因为拷贝的代价比较大,零拷贝技术诞生了。

零拷贝

零拷贝(Linux中的sendfile方法):应用程序每调一次程序调用(api),给网卡一个socket连接和一个内核的文件描述符(Linux的万物皆文件),内核直接读取文件写到用户空间,减少了一次拷贝到内核的过程。

何为IO:

现在许多的中间件和服务器都实现了IO的多路复用,例如redis的worker()、nginx、netty(可选)等。
注:redis6.0后支持多线程,主要是体现在分工于IO读写(并非完全并行),提升效率的同时也避免了脏数据。

IO的本质其实就是从本机硬盘读写数据,或者通过网卡在网络上传输数据等行为的统称(input output…)

普通的一次IO,程序通过建立一个socket文件(描述符为fd5),绑定一个端口(假设为8090),之后去监听这个端口,一旦有连接进来就通过accept()来接收,这时客户端被描述为fd6(Linux一切皆文件),这时如果调用read/recvfrom而一直未发数据就会一直阻塞在这个地方。

普通的一次IO过程如图:
IO

BIO(Blocking IO):

BIO    系统早期的Bio阻塞接收数据 ,主线程使用socket.accept()来监听客户端的连接,每次得到一个新的连接就创建一个新线程让新线程去读取并且处理客户端发送的数据。

   每一个线程对应一个client,这种方式系统调用过多,让每一个线程都去clone系统调用的方法,并且过多的线程会过度占用程序的资源(java默认线程的栈大小1M),而且来回切换线程也很浪费时间。

   这些问题的本质就是因为Blocking(阻塞)。这种方式会造成C10K问题,1W个客户端会造有1W个线程监听,机器负载压力过大,每循环内有O(n)复杂度的systemcall(系统调用),且大量的系统调用,可能并不会获得数据(可能一万次遍历只有几个调用有传输数据)。

NIO(poll/selector):

NIO

   Nio有两层意思,应用程序级别是指New IO(channel/buffer等新的IO体系),而内核级别则是NoBlocking IO
   因为BIO不能满足O(m)复杂度的系统调用(m即确定需要读写的客户端数,而非总客户端数),其会造成许多次无用的系统调用。此时就有了新的技术——多路复用器(select/poll),每次只需一次系统调用传入所有的fds(即文件描述符)与它们是否可读可写的状态,内核去主动遍历所有的描述符。之后再进行O(m)次的系统调用(system call)去读写客户端的数据。

   缺点:每一次系统调用都要传递大量的文件描述符给内核,并且每次都要遍历所有的数据。

   等待数据和将数据复制到用户进程这两个阶段都是阻塞的,而大量的连接在阻塞前可与Selector建立连接,这即是IO多路复用(selector/poll, selector使用bitmap,故最多监听1024个进程;poll使用数组,于是可以不限制具体长度),。
如:(一个连接来了,就必须遍历所有已经注册的文件描述符,来找到那个需要处理信息的文件描述符,如果已经注册了几万个文件描述符,那会因为遍历这些已经注册的文件描述符,导致cpu爆炸。)

epoll(event poll 事件驱动的IO):

epoll

   在内核内开辟一个属于epoll的内核空间(调用epoll_create),然后先将监听客户端连接的进程移入新空间(epoll_ctl),然后再调用epoll_wait等待通知需要进行读写等操作的客户端,其将结果返回给应用程序让其去完成实际读写,之后每当有客户端连入的时候将客户端的文件描述符添加进这个空间中。
   其中客户端连入读写事件使用网卡中断,通过电信号告诉cpu而不需要cpu自己去遍历,这样充分利用了cpu资源。(事件驱动模型中,处理事件的进程大部分情况是单线程的。)

   每次新线程加入连接,积压入内核,就不需要每次的重复遍历文件描述符。有数据则写入缓存区(分 读 ——写缓存区),待使用则再IO。可以将描述符对应放入等待队列,然后epoll_wait阻塞执行真正的IO读写。

底层使用的数据结构:红黑树

题外话:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXcGsIUU-1602049415652)(https://media.giphy.com/media/f4V2mqvv0wT9m/giphy.gif)]

介绍一个讲源码的b站up主,这个up我关注挺久了,以前也是瑞幸的架构师,现在我也在他的群里(群里都是工作多年的大佬或高校的学生,我瑟瑟发抖)。
他有一期就是讲IO多路复用的面试,可以去听一下。

UP:小刘讲源码

还有这篇文章其实我是从马士兵教育里面免费公开课有一期讲解IO多路复用里面截取来的,以上算是个人的总结,对知识的一个温故而知新吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值