不落辰

知不可乎骤得,托遗响于悲风

0%

Linux IO模型

阻塞、非阻塞、同步、异步

网络IO阶段一:数据准备

  • 数据准备:根据系统IO操作的就绪状态。
    • 阻塞 blocking
    • 非阻塞 non-blocking
  • 解释
    • 比如API recv
    • 远端是否有数据过来,也即内核对应的sockfd的TCP缓冲区是否有数据可读
    • 阻塞时,如果远端没有数据过来,那么recv会阻塞在那里。
    • 非阻塞时,无论有没有数据过来,recv都会立刻返回。通过recv的返回值,判断状态。
      • size == -1 && errno = EAGAIN 非阻塞没数据
      • size = 0 网络对端关闭
      • size > 0 读到的bytes数

网络IO阶段二:数据读取

  • 数据读写:根据应用程序和内核的交互方式

    • 同步 synchronous
    • 异步 asynchronous
  • 解释

  • 同步 synchronous

    • API recv
    • arg传sockfd,buf
    • (无论阻塞和非阻塞时),如果数据就绪了,sockfdkernel的TCP缓冲区中有数据,那么会将os的内核缓冲区拷贝到应用程序的buf缓冲区,这段时间花的是应用程序自己的时间。等待直到拷贝完成后,recv才返回,应用程序才能向后走。
  • 异步 asynchronous

    • 传sockfd,buf,通知方式
    • os负责监听,tcp缓冲区是否有数据可读,有的话,则内核替我将数据从内核缓冲区拷贝到我传给他的buf。拷贝完后再通过约定好的通知方式通知我。这段拷贝的时间是其他进程花费的时间。不是本进程花费的时间
  • 也即同步需要自己监听 事件什么时候完成(数据从内核缓冲区被拷贝到用户缓冲区)

  • 而异步不需要,当事情完成后,会有别人通知(数据的监听,数据的搬运(读写)都不需要应用程序来花费时间,都是内核做的)

  • 业务的同步异步
    • 与IO的同步异步原理相同
    • 是业务层面的处理逻辑
    • 同步:A发起请求,等待B操作做完事情,得到返回值,继续处理。
    • 异步:A操作告诉B操作它感性确定时间以及通知方式,A操作继续执行自己的业务逻辑了;等B监听到相应的事件发生后,B会通知A,A开始相应的数据处理逻辑。
  • 总结
    • 同步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞);
    • 异步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果
  • 同步阻塞:int sz = recv(fd,buf,SIZE,0);

  • 同步非阻塞:int sz = recv(fd,buf,SIZE,非阻塞);

  • 异步阻塞:理论上有,但完全就是浪费时间,没必要,没人用。

  • 异步非阻塞:异步一般都是非阻塞。

  • 描述逻辑

    • 用一个IOAPi来讲,比如read/recv
    • 在阻塞/非阻塞下的表现形式
    • 接下来再介绍同步、异步(特殊API aio_read)

陈硕大神原话:在处理 IO 的时候,阻塞和非阻塞都是同步 IO。只有使用了特殊的 API 才是异步
IO。

Linux五种IO模型

Blocking IO

  • 同步阻塞
  • 一直阻塞 效率不高。
  • 分为两阶段
    • 等待数据就绪
    • 拷贝数据从kernel buf -> usr buf

non-Blocking IO

  • 同步非阻塞
  • 我的问题:
    • 真的,就在这里不断轮询有什么用?浪费时间还占用cpu。不如把cpu让给别的线程,在这非阻塞轮询还不如阻塞呢。那非阻塞有什么用?
  • 解决:
    • non-blocking一般是和IO复用配合使用

IO复用(IO multiplexing)

  • epoll/select/poll:同步。异步最大的特点是协商了一个通知事件机制,这里明显没有,就是在等待。

  • read同步

  • IO复用比起轮询和阻塞好在哪?

    • 一次返回多个事件,从而不用多次在内核态和用户态之间切换。

Signal-Driven 信号驱动

  • 在第一个阶段异步,在第二个阶段同步。
  • 如果阻塞的话,需要一直阻塞
  • 如果非阻塞的话,需要一直轮询检查
  • 而信号通知机制,使得我们不必阻塞也不必轮询检查,可以去做应用程序自己的事情,同时减少了系统API的调用次数,提高效率。
  • 很少用。因为信号异步 不好处理

Asychronous 异步

1
2
3
4
5
6
7
8
9
struct aiocb {
int aio_fildes
off_t aio_offset
volatile void *aio_buf
size_t aio_nbytes
int aio_reqprio
struct sigevent aio_sigevent
int aio_lio_opcode
}
  • 异步非阻塞,根本不消耗应用程序的时间,应用程序完全可以去做其他事情,等待内核通知即可
  • 编写困难。

one loop per thread

  • one loop per thread is usually a good model ——libv作者
    • 如果采用one loop per thread的模型,多线程服务器端编程就简化为如何设计一个高效,且易于使用的event loop,然后每个thread run 一个 event loop就行了。 再加上同步、互斥等。
  • event loop 是 non-blocking 网络编程的核心

non-blocking & IO multiplexing

  • 在现实生活中,non-blocking 几乎总是和 IO multiplexing 一起使用,原因有两点:
    • 单单non-blocking:不断轮询busy looping检查某个non-blocking IO操作是否完成,耗费时间,浪费CPU。没人这么做的
    • 单单IO-multiplex:IO-multiplex 一般不能和 blocking IO 用在一起,因为 blocking IO 中 read()/write()/accept()/connect() 都有可能阻塞当前线程,这样线程就没办法处理其他 socket上的 IO 事件了。
      • 例如:epoll返回一个可读事件,用户程序进行读取,会一直循环直到读完这个可读fd,如果我们用的是阻塞的socket的话,那么在read读完之后就会阻塞在这个read上,本线程在这里阻塞住,程序无法继续向下走,也无法返回到epoll中。
  • 所以,当我们提到 non-blocking 的时候,实际上指的是non-blocking + IO-multiplexing,单用其中任何一个都没有办法很好的实现功能。