IO模型学习笔记

这里写图片描述

IO模式

对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
所以说,当一个read操作发生时,它会经历两个阶段
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

阻塞 I/O

阻塞IO执行的两个阶段都被block。

非阻塞 I/O

  • nonblocking IO:用户进程需要不断的主动询问kernel数据好了没有,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回0,用户进程发现是0,可以过会再询问,一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,,那么它马上就将数据拷贝到了用户内存,然后返回。注意在第二阶段其实仍旧是阻塞的。
  • NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来,记录的方式通常是在Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。
  • NIO是多路复用IO的基础。
  • 成熟的NIO框架,如Netty,MINA等

异步 I/O

没啥好说的,顾名思义。

I/O 多路复用

也称事件驱动IO。
它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,保证cpu不会空转。
select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。

select

缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。

poll

和select一样通过遍历文件描述符来获取已经就绪的socket,虽然没有最大数量限制,但是数量多了会造成性能下降,因为要遍历socket。

epoll

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式
  LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
  ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
  
如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle- connection,就会发现epoll的效率大大高于select/poll。

Proactor与Reactor

一般情况下,I/O 复用机制需要事件分发器(event dispatcher)。 事件分发器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁谁谁的快递到了, 快来拿吧!开发人员在开始的时候需要在分发器那里注册感兴趣的事件,并提供相应的处理者(event handler),或者是回调函数;事件分发器在适当的时候,会将请求的事件分发给这些handler或者回调函数。

涉及到事件分发器的两种模式称为:Reactor和Proactor。 Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的。

Reactor模式
  • 简单实现:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。
   interface ChannelHandler{
      void channelReadable(Channel channel);
      void channelWritable(Channel channel);
   }
   class Channel{
     Socket socket;
     Event event;//读,写或者连接
   }

   //IO线程主循环:
   class IoThread extends Thread{
   public void run(){
   Channel channel;
   while(channel=Selector.select()){//选择就绪的事件和对应的连接
      if(channel.event==accept){
         registerNewChannelHandler(channel);//如果是新连接,则注册一个新的读写处理器
      }
      if(channel.event==write){
         getChannelHandler(channel).channelWritable(channel);//如果可以写,则执行写事件
      }
      if(channel.event==read){
          getChannelHandler(channel).channelReadable(channel);//如果可以读,则执行读事件
      }
    }
   }
   Map<Channel,ChannelHandler> handlerMap;//所有channel的对应事件处理器
  }
  //由原来的阻塞读写(占用线程)变成了单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。
  • Java的Selector对于Linux系统来说,有一个致命限制:同一个channel的select不能被并发的调用。因此,如果有多个I/O线程,必须保证:一个socket只能属于一个IoThread,而一个IoThread可以管理多个socket。
  • Redis基于Reactor模式开发了以单线程的方式运行的网络事件处理器,通过IO复用来监听多个套接字,实现了高性能的网络通讯模型,又很好的与Redis服务器中其他同样以单线程方式运行的模块进行对接,保持了内部线程设计的简单性(参考Redis设计与实现第12章12.1文件事件)。使用单线程+队列,保证服务端是全局串行的,能够保证同一连接的所有请求与返回顺序一致。
    这里写图片描述
    图中的IO多路复用程序即网络事件处理器,它是单线程的,基于Reactor模式。另外命令的处理过程也是主线程处理的,如果此时有新的请求到来,会暂时不理,等到event_loop执行到下一次从缓冲区读取新的数据, 然后再处理,期间新的请求数据只能在socket缓冲区里standby。具体看http://www.hoterran.info/redis_eventlibrary
  • 使用该IO != 高性能,当连接数<1000,并发程度不高或者局域网环境下该IO类型并没有显著的性能优势。
    多路复用并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。
Proactor
区别
  • 同步情况下(Reactor),回调handler时,表示I/O设备可以进行某个操作(can read 或 can write)。异步情况下(Proactor),当回调handler时,表示I/O操作已经完成,即IO的第二阶段由操作系统来完成了,有些系统不支持底层异步API,可以去模拟实现。
  • 对于一个读事件
    标准/典型的Reactor:
    步骤1:等待事件到来(Reactor负责)。
    步骤2:将读就绪事件分发给用户定义的处理器(Reactor负责)。
    步骤3:读数据(用户处理器负责)。
    步骤4:处理数据(用户处理器负责)。
    改进实现的模拟Proactor:
    步骤1:等待事件到来(Proactor负责)。
    步骤2:得到读就绪事件,执行读数据(现在由Proactor负责)。
    步骤3:将读完成事件分发给用户处理器(Proactor负责)。
    步骤4:处理数据(用户处理器负责)。

参考

https://segmentfault.com/a/1190000003063859
http://tech.meituan.com/nio.html
http://blog.jobbole.com/59676/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值