常⽤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)模型,由于该模型并不常⽤(略)
在理解关于同步和阻塞的概念前,需要知道
ajax是同步还是异步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(或2
G)内存。
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,感兴趣的读者可以尝试使⽤。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论