阻塞、非阻塞、同步、异步
网络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 | struct aiocb { |
- 异步非阻塞,根本不消耗应用程序的时间,应用程序完全可以去做其他事情,等待内核通知即可
- 编写困难。
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,单用其中任何一个都没有办法很好的实现功能。