什么是同步I/O,什么是异步I/O

  • 同步(阻塞)I/O:在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。这种情况称为同步IO。
  • 异步(非阻塞)I/O:当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

事件处理模式

reactor模式中,主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话立即通知工作线程(逻辑单元 ),读写数据、接受新连接及处理客户请求均在工作线程中完成。通常由同步I/O实现。
proactor模式中,主线程和内核负责处理读写数据、接受新连接等I/O操作,工作线程仅负责业务逻辑,如处理客户请求。通常由异步I/O实现。
模拟proactor模式:主线程执行数据读写操作,读写完后,主线程向工作线程通知这以完成事件,接下里工作线程对读写的结果进行处理

主线程往epoll内核事件表注册socket上的读就绪事件。
主线程调用epoll_wait等待socket上有数据可读
当socket上有数据可读,epoll_wait通知主线程,主线程从socket循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入请求队列。
睡眠在请求队列上某个工作线程被唤醒,它获得请求对象并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件
主线程调用epoll_wait等待socket可写。
当socket上有数据可写,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果。

工作模式

epoll 的描述符事件有两种触发模式:LT(level trigger)和 ET(edge trigger)。

1. LT 模式

当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。

2. ET 模式

和 LT 模式不同的是,通知之后进程必须立即处理事件,下次再调用 epoll_wait() 时不会再得到事件到达的通知。
很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

5中I/O模型

  • 阻塞IO:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作
  • 非阻塞IO:非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。非阻塞I/O执行系统调用总是立即返回,不管时间是否已经发生,若时间没有发生,则返回-1,此时可以根据errno区分这两种情况,对于accept,recv和send,事件未发生时,errno通常被设置成eagain
  • 信号驱动IO:linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件。
  • IO复用:linux用select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或可写时,才真正调用IO操作函数
  • 异步IO:内核执行读写操作并触发读写完成事件,程序没有阻塞阶段。

注意:阻塞I/O,非阻塞I/O,信号驱动I/O和I/O复用都是同步I/O。同步I/O指内核向应用程序通知的是就绪事件,比如只通知有客户端连接,要求用户代码自行执行I/O操作,异步I/O是指内核向应用程序通知的是完成事件,比如读取客户端的数据后才通知应用程序,由内核完成I/O操作。

半同步/半反应堆并发模式是半同步/半异步的变体,将半异步具体化为某种事件处理模式

select、poll、epoll

I/O复用虽然可以同时监听多个文件描述符,但是它本身是阻塞的,并且当有多个文件描述符同时就绪的时候,如果不采取额外措施,程序则只能按顺序处理其中就绪的每一个文件描述符,所以为提高效率,可以通过线程池来实现并发(多线程并发),为每个就绪的文件描述符分配一个逻辑单元(线程)来处理。

你用了epoll,说一下为什么用epoll,还有其他复用方式吗?区别是什么?

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间

select

select返回(可读、可写和异常)文件描述符的总数,如果超时没有任何文件描述符就绪,返回0,失败返回-1并设置errno

  • 调用 select ,对所有文件描述符循环,检测就绪事件

  • 每次调用 select 函数时都需要向该函数传递监视对象信息,

  • 每次调用select,都需将全部描述符从应用进程缓冲区复制到内核缓冲区。

    • select支持的文件描述符数量太小了,默认是1024

poll

poll的实现和select非常相似,只是描述文件描述符集合的方式不同,poll使用pollfd结构数组指定文件描述符上发生的可读、可写和异常等事件,其他的都差不多。几乎所有的系统都支持 select,但是只有比较新的系统支持 poll

epoll

  • epoll把文件用户关心的文件描述符上的事件放在内核里的一个事件表中,无须像select和poll每次调用都要重复传入文件描述符集或事件集
  • epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次,并且进程不需要通过轮询来获得事件完成的描述符。
  • epoll 仅适用于 Linux OS, 比 select 和 poll 更加灵活而且没有描述符数量限制。

Epoll对文件操作符的操作有两种模式:LT(电平触发)和ET(边缘触发),二者的区别在于当你调用epoll_wait的时候内核里面发生了什么:

  • LT(电平触发):类似select,LT会去遍历在epoll事件表中每个文件描述符,来观察是否有我们感兴趣的事件发生,如果有(触发了该文件描述符上的回调函数),epoll_wait就会以非阻塞的方式返回。若该epoll事件没有被处理完(返回EWOULDBLOCK),该事件还会被后续的epoll_wait再次触发。
  • ET(边缘触发):ET在发现有我们感兴趣的事件发生后,立即返回,并且sleep这一事件的epoll_wait,不管该事件有没有结束。
    在使用ET模式时,必须要保证该文件描述符是非阻塞的(确保在没有数据可读时,该文件描述符不会一直阻塞);并且每次调用read和write的时候都必须等到它们返回EWOULDBLOCK(确保所有数据都已读完或写完)

两种高效的并发模式

并发编程的目的:为了让程序执行多个任务

计算密集型:并发编程没有优势,反而由于任务的切换导致效率低下

I/O密集型:I/O操作的速度远没有CPU快,让程序阻塞于I/O操作将浪费大量的CPU时间。如果程序有多个线程,则当前被I/O操作所阻塞的执行线程可主动放弃CPU,并将执行权转移到其他线程

半同步/半异步模式

I/O模型中:同步和异步区分:内核向应用进程通知的是何种事件(是就绪事件还是完成事件),以及由谁来完成I/O操作(是应用进程还是内核)

并发模式中中

  • 同步:程序完全按照代码序列的顺序执行
  • 异步:程序的执行需要系统事件来驱动,常见系统事件包括:中断、信号等

半同步/半异步模式中,同步线程用于处理客户逻辑,异步线程用于处理I/O事件

领导者/追随者模式

线程数多少合适

使用多线程的主要目的提高性能:降低延迟 (发送请求到接收到数据的时间)和提高吞吐量 (单位时间能可以处理更多的请求)

降低延迟和提高吞吐量对应的方法有两种: 优化算法 和 将机器的硬件性能发挥到极致

  • 优化算法:降低时间和空间复杂度,使得程序执行时间更短。
  • 将硬件的性能发挥到极致,具体的指提高I/O和cpu的利用率

如何提高I/O 和cpu的利用率的举例:

  • 如果单核系统中,只有一个程序执行又有IO操作 和Cpu计算的代码,当程序执行IO操作的时候,Cpu其实是空闲的,反之IO是空闲的,如果这个时候用两个线程去跑这段代码,一个线程执行IO操作 ,一个线程执行Cpu计算 ,这时IO和Cpu的利用率是不是发挥了极致?

在这里插入图片描述

空间换时间,浪费服务器的硬件资源,换取运行效率.
池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化,这称为静态资源.
当服务器进入正式运行阶段,开始处理客户请求的时候,如果它需要相关的资源,可以直接从池中获取,无需动态分配.
当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用释放资源.

gevent

Gevent是一个基于greenlet的Python的并发框架,以微线程greenlet为核心,使用了epoll事件监听机制以及诸多其他优化而变得高效。

于greenlet、eventlet相比,性能略低,但是它封装的API非常完善,最赞的是提供了一个monkey类,可以将现有基于Python线程直接转化为greenlet,相当于proxy了一下(打了patch)。

功能包括:

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐