常用4种IO模型(同步/异步/阻塞/非阻塞的概念)

常见的IO模型有四种:

服务器端编程经常需要构造高性能的IO模型

1)同步阻塞IO(Blocking IO):即传统的IO模型。
(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。
注意这里所说的NIO并非Java的NIO(New IO)库。
3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。 (4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。
(5)基于信号驱动的IO(Signal Driven IO)模型,由于该模型并不常用(略)

在理解关于同步和阻塞的概念前,需要知道

I/0 操作 主要分成两部分
① 数据准备,将数据加载到内核缓存(数据加载到操作系统)
② 将内核缓存中的数据加载到用户缓存(从操作系统复制到应用中)

同步和异步的概念描述的是用户线程与内核的交互方式

阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式

异步就是异步

来源:关于同步、异步与阻塞、非阻塞的理解

同步和异步针对应用程序来,关注的是程序中间的协作关系;

阻塞与非阻塞更关注的是单个进程的执行状态。

 阻塞、非阻塞、多路IO复用,都是同步IO,异步必定是非阻塞的,所以不存在异步阻塞和异步非阻塞的说法。
真正的异步IO需要CPU的深度参与。换句话说,只有用户线程在操作IO的时候根本不去考虑IO的执行全部都交给CPU去完成,
而自己只等待一个完成信号的时候,才是真正的异步IO。
所以,拉一个子线程去轮询、去死循环,或者使用select、poll、epool,都不是异步。

PS:

1,异步是一个相对概念,实际应用中没有绝对的异步,现实中更多称为“异步”只是代表阻塞。
2,不同场合,语言环境,概念不一样,有时候同步就代表了阻塞,异步表示非阻塞。如果细分的话,代表不同含义。

PS:【同步/异步】和【阻塞/非阻塞】的关注点是存在区别的:

【同步/异步】表示是两个事件交互的是否有序依赖关系
同步:针对执行结果,A事件必须知道B事件的结果M后才执行得到结果。
异步:针对执行结果,执行A事件和执行B事件没有关系。
阻塞
/非阻塞表示执行过程出现的状态 阻塞:针对执行者来说,执行A事件,执行过程因为条件未满足,执行状态变成等待状态。 非阻塞:针对执行者来说,就是事件A执行遇到未满足条件,执行另外独立的C事件。
总结:两者之间是没有关系的 【同步
/异步】
概念上是:事件A,B的结果之间的是否存在依赖关系;
影响上是:保证依赖数据的正确性 【阻塞
/非阻塞】
概念上是:自身执行状态。 影响上是:阻塞导致资源浪费。

特别注意:异步只有异步,同步才有阻塞和非阻塞的说法!

例子: 总整体看:传统的请求,是同步的(也是阻塞的),请求响应是有序的(请求响应之间也是等待的);AJAX是异步请求(也是非阻塞的)。 同步不等于阻塞: 单个看:AJAX从客户端执行单个请求看数据是同步,但是执行是非阻塞,在未收到响应继续执行其他请求。

 

一、同步阻塞IO

同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时被阻塞。

 

用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。

内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。

二、同步非阻塞IO

同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NIO(NONBLOCK)。

这样做用户线程可以在发起IO请求后可以立即返回。

由于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。

但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。

三、IO多路复用

IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,

使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。
当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,
甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。
但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。
用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。
而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
然而,使用select函数的优点并不仅限于此。
虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。
如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

IO多路复用模型使用了Reactor设计模式实现了这一机制。

补充:IO多路复用又叫“事件驱动”

首先,要从你常用的IO操作谈起,比如read和write,通常IO操作都是阻塞I/O的,也就是说当你调用read时,
如果没有数据收到,那么线程或者进程就会被挂起,直到收到数据。 这样,当服务器需要处理1000个连接的的时候,而且只有很少连接忙碌的,
那么会需要1000个线程或进程来处理1000个连接,而1000个线程大部分是被阻塞起来的。
由于CPU的核数或超线程数一般都不大,比如4,
8,16,32,64,128,比如4个核要跑1000个线程,那么每个线程的时间槽非常短,而线程切换非常频繁。
这样是有问题的:
1,线程是有内存开销的,1个线程可能需要512K(或2M)存放栈,那么1000个线程就要512M(或2G)内存。
2,线程的切换,或者说上下文切换是有CPU开销的,当大量时间花在上下文切换的时候,分配给真正的操作的CPU就要少很多。 那么,我们就要引入非阻塞I
/O的概念,非阻塞IO很简单,通过fcntl(POSIX)或ioctl(Unix)设为非阻塞模式,
这时,当你调用read时,如果有数据收到,就返回数据,如果没有数据收到,就立刻返回一个错误,如EWOULDBLOCK。
这样是不会阻塞线程了,但是你还是要不断的轮询来读取或写入。 于是,我们需要引入IO多路复用的概念
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,
如果有一个文件描述符就绪,则返回,否则阻塞直到超时。
得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。 这样在处理1000个连接时,只需要1个线程监控就绪状态,对就绪的每个连接开一个线程处理就可以了,
这样需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。

四、异步IO

“真正”的异步IO需要操作系统更强的支持。
在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。
而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

 

异步IO模型使用了Proactor设计模式实现了这一机制。

相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。
况且目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式
(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。
Java7之后已经支持了异步IO,感兴趣的读者可以尝试使用。

 

NIO技术概览 

Socket高性能IO模型浅析

转 网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO 

 网络编程:Reactor与Proactor的概念

 

 

 

 

posted @ 2018-08-15 16:14  假程序猿  阅读(12674)  评论(6编辑  收藏  举报