协程与多路复用IO

协程和多路复用IO都是解决大量线程进行IO操作带来的性能问题!

协程出现的原因

线程太多会出现的问题

一是系统线程会占用非常多的内存空间,二是过多的线程切换需要CPU用户态和核心态的切换会消耗大量系统时间
在这里插入图片描述

协程成为线程中运行的实体

  • 协程由用户程序按需创建
  • 我们把任务分发给协程,协程有自己的执行环境(执行现场)存在于线程中
  • 一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。
  • 协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程。
  • 而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

在这里插入图片描述

在这里插入图片描述

解决的问题

以前需要开启10000个线程执行的任务,现在只需要启动100个线程,每个线程上运行100个协程。

这样不仅减少了线程切换、内存开销,而且还能够同时处理10000个任务。

协程执行过程

但对于操作系统,仍然面对着线程进行调度
在这里插入图片描述

线程切换与协程切换对比

线程切换:

  • 线程在进行切换的时候,需要将CPU中的寄存器的信息存储起来,然后读入另外一个线程的数据,这个会花费一些时间
  • CPU的高速缓存中的数据,也可能失效,需要重新加载
  • 线程的切换会涉及到用户模式到内核模式的切换,据说每次模式切换都需要执行上千条指令,很耗时。

协程切换:

  • 在切换的时候,寄存器需要保存和加载的数据量比较小
  • 高速缓存可以有效利用
  • 没有用户模式到内核模式的切换操作。
  • 更有效率的调度,因为协程是非抢占式的,前一个协程执行完毕或者堵塞,才会让出CPU而线程则一般使用了时间片的算法,会进行很多没有必要的切换(为了尽量让用户感知不到某个线程卡)。

总结

在有大量IO操作业务的情况下,我们采用协程替换线程,可以到达很好的效果,一是降低了系统内存,二是减少了系统切换开销,因此系统的性能也会提升。

在协程中尽量不要调用阻塞IO的方法,比如打印,读取文件,Socket接口等,除非改为异步调用的方式,并且协程只有在IO密集型的任务中才会发挥作用。(因为操作系统不知道协程的存在,如果协程调用阻塞IO,会让整个线程进入阻塞。。。。这玩了个锤子呦)

协程只有和异步IO结合起来才能发挥出最大的威力。


什么是IO?

I/O就是数据的输入(Input)、输出(Output),且IO操作是系统调用(数据要在用户空间与内核空间之间拷贝)
针对不同的操作对象,可以划分为磁盘I/O模型网络I/O模型,内存映射I/O, Direct I/O、数据库I/O等。

如由南桥的DMA进行IO操作,将数据从外存中进行读取、存储:(网络IO:DMA读取网卡中数据到网卡缓冲区内存 ===网卡硬件中断=> CPU将缓存区数据根据socket放到不同的socket缓冲区
在这里插入图片描述

文件描述符fd:系统中有好多输入、输出流,,每个流使用文件描述符fd标识(fd就是个整数),对流的操作就是对这个整数进行操作!!

IO模型分类

网络IO为例: 线程读取 / 写入网卡缓冲区数据
在这里插入图片描述

阻塞IO

当一个线程发起IO请求,这个请求(系统调用)就会一直阻塞,直至可用时(允许读 / 写)才会返回,阻塞期间线程干不了其他事。

非阻塞IO

当一个线程发起IO请求,系统调用会立即返回是否可用(允许读 / 写),如果返回不可用,然后线程会循环进行请求,直至请求返回可用。

IO多路复用

面试:什么是IO多路复用?

答:IO多路复用是为解决大量线程进行IO请求,导致CPU主要处理了IO线程的切换(用户态和核心态转换)而损耗的大量性能问题。
首先,先引入一个中间件,也就是select、epoll等。这些中间件会有一个专门的IO线程监听所有连接,替所有线程查看数据状态,当有数据准备就绪之后再分配对应的线程去读取数据。

出现的问题:好多线程进行IO请求,那么CPU主要处理了大量线程的IO请求,而且线程切换需要CPU用户态和核心态的切换,这就会消耗大量系统资源

注:在网络连接中,一个连接就是一个流啊!就是一个fd啊!

在这里插入图片描述

解决的办法:IO多路复用,引入一个中间代理,由一个线程专门监控多个网络连接(后面将称为fd文件描述符),这样就可以只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据。

在这里插入图片描述

多路IO的实现(代理:其实就是系统提供的一种函数)

复用IO的基本思路就是通过select或poll、epoll(系统调用函数) 来监控多fd ,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。 (这个代理是一个进程一个select或poll、epoll,是将一个进程中的IO请求由一个IO线程进行处理)【Redis、Nginx 都是epoll】

select

在这里插入图片描述

select
实现过程1. 连接集合fd集合保存着需要监听的fd
2. 将fd集合转换为rset集合(底层是bitmap标识位数据结构 大小1024)
3. 调用select()函数传入rset集合参数(将rset从用户态拷贝到内核态进行遍历监听是否有数据(fd的poll()函数)===> 如果没有数据监控线程则会睡眠一会,然后又继续遍历 ===> 如果有数据,则将有数据的fd标志(置位),然后将rset从内核空间拷贝到用户空间) select函数返回(select是个阻塞函数)
4. 遍历查看rset集合哪个fd有数据(被置位的),并进行读操作(结束后从2又头开始)
时间复杂度线性遍历返回的fd集合:O(n)
最大连接数32位机默认是1024个,64位机默认是2048。
性能1. 每次select都需要用户态与内核态拷贝fd集合
2. rset集合不能重用,要根据fd集合重新赋值
poll
poll
实现过程调用过程和select类似,只是采用链表的方式替换原有fd_set数据结构,而使其没有连接数的限制。
时间复杂度线性遍历返回的结果集合:O(n)
最大连接数无限制
性能1. 每次poll都需要用户态与内核态拷贝rset集合
2. rset集合不能重用,要根据fd集合重新赋值
3. 水平触发:若是报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
epoll

在这里插入图片描述

epoll结构就在内核态中:

在这里插入图片描述

socket等待队列都指向了epoll:
在这里插入图片描述

会将自己的fd加入到就绪队列中:
在这里插入图片描述

然后,就绪队列中的fd会被拷贝到epoll_wait()函数传入的数组中并返回,然后我们遍历数组中的fd就ok

epoll
工作方式两种工作方式:水平触发(LT)与边缘触发(ET)
水平触发模式:会将没有处理完的事件继续放回到就绪队列之中(即那个内核中的链表),一直进行处理。
边缘触发模式:就绪的事件只能处理一次,若没有处理完会在下次的其它事件就绪时再进行处理。而若以后再也没有就绪的事件,那么剩余的那部分数据也会随之而丢失。 ET模式只支持非阻塞的读写:为了保证数据的完整性。
由此可见:边缘触发模式效率要高。只是如果使用边缘触发模式,就要保证每次进行数据处理时,要将其处理完,不能造成数据丢失。【边缘模式的优点:系统不会充斥大量你不关心的就绪文件描述符 虽然有数据,但你并不想处理。】
时间复杂度直接从就绪链表中拿数据:O(1)
最大连接数有上限 很大,1G内存的机器上可以打开10万左右的连接
性能1. select和poll在“醒着”的时候要遍历整个fd集合;而epoll在只要判断一下就绪链表是否为空就好了 ,节省了大量的CPU时间
2. epoll不会因为连接数增加而效率递减,因为epoll不会去遍历所有的fd
3. fd就绪后,回调函数(fd上的callback函数)会被调用,于是fd会被加入到就绪队列中
4. epoll使用mmap实现内核空间与用户空间共享,避免了fd拷贝
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值