• IO阻塞与非阻塞


    同步:
          
    所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

    例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事

    异步:
          
    异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

         例如 ajax请求(异步)请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

    阻塞
         
    阻塞调用是指调用结果返回之前,当前线程会被挂起线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

         有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket中调用recv函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。

    非阻塞
          
    非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
    对象的阻塞模式和阻塞函数调用
    对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状 态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。

    IO多路转接

          它也是会阻塞,不过它是可以阻塞多个IO,实现近似于非阻塞IO,先构造一张有关描述符的数据表,然后调用一个函数,仅当有一个或多个描述符已准备可以进行IO操作时才返回,否则一直阻塞。在返回时,它会告诉进程那些描述符已准备好可以进行IO

      select函数的参数将告诉内核:

      (1) 我们所关心的描述符。

      (2) 对于每个描述符我们所关心的条件(是否读一个给定的描述符?是否想写一个给定的

    描述符?是否关心一个描述符的异常条件?)。

      (3) 希望等待多长时间(可以永远等待,等待一个固定量时间,或完全不等待)

            select从内核返回后内核会告诉我们:

      (1) 已准备好的描述符的数量。

      (2) 哪一个描述符已准备好读、写或异常条件。

            select 用于查询设备的状态,以便用户程序获知是否能对设备进行非阻塞的访问,需要设备驱动程序中的poll 函数支持。 驱动程序中 poll 函数中最主要用到的一个 API 是 poll_wait,其原型如下:

      void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);

      poll_wait 函数所做的工作是把当前进程添加到 wait 参数指定的等待列表(poll_table)中。

      需要说明的是,poll_wait 函数并不阻塞,程序中 poll_wait(filp, &outq, wait)这句话的意思并不是说一直等待 outq 信号量可获得,真正的阻塞动作是上层的 select/poll 函数中完成的。select/poll 会在一个循环中对每个需要监听的设备调用它们自己的 poll 支持函数以使得当前进程被加入各个设备的等待列表。若当前没有任何被监听的设备就绪,则内核进行调度(调用 schedule)让出 cpu 进入阻塞状态,schedule 返回时将再次循环检测是否有操作可以进行,如此反复;否则,若有任意一个设备就绪,select/poll 都立即返回。

      内核中实现select是用轮询方法

    epoll

      1>支持一个进程打开大数目的socket描述符(FD)

      2>IO效率不随FD数目增加而线性下降,它的内核实现只会对"活跃"的socket进行 操作,而不是全部轮询

      3>使用mmap加速内核与用户空间的消息传递。epoll是通过内核于用户空间mmap同一块内存实现来进行消息通知的。

    Nginx并发策略

      ginx使用的是一个进程处理多个连接、非阻塞IO模式 ,这种模式最特别的是设计了独立的listener进程,专门负责接收新的连接,再分配给各个worker,当然为了减少任务调度的开销,一般都是由worker进程来进行接收。在listener进程中使用epoll来进行非阻塞IO。

    信号驱动IO

        简介:两次调用,两次返回;

        首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

    异步IO模型

             简介:数据拷贝的时候进程无需阻塞。

         当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作

    5个I/O模型的比较:

     java NIO

      nio 是java nonblocking(非阻塞) IO 的简称

      *为所有的原始类型提供(Buffer)缓存支持。

      *字符集编码解码解决方案。

      *Channel :一个新的原始I/O 抽象

      *支持锁和内存映射文件的文件访问接口。

      *提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。

      1.缓冲Buffer

      是一个固定数据量的指定基本类型的数据容器。除内容之外,缓冲区还具有位置 和界限,其中位置是要读写的下一个元素的索引,界限是第一个应该读写的元素的索引。基本 Buffer 类定义了这些属性以及清除、反转 和重绕 方法,用以标记 当前位置,以及将当前位置重置 为前一个标记处。
      每个非布尔基本类型都有一个缓冲区类。每个类定义了一系列用于将数据移出或移入缓冲区的 get 和 put 方法,用于压缩、复制 和切片 缓冲区的方法,以及用于分配 新缓冲区和将现有数组包装 到缓冲区中的静态方法。
      因为字节缓冲区可以用作 I/O 操作的源缓冲区和目标缓冲区,所以可以对它们加以区分。它们还支持其他缓冲区类所没有的几个特性:可以将字节缓冲区分配为一个直接 缓冲区,在这种情况下,Java虚拟机将最大限度地直接在缓冲区上执行本机 I/O 操作。
      可以通过 mapping 将文件区域直接包装到内存中来创建字节缓冲区,在这种情况下,可以使用 MappedByteBuffer 类中定义的几个其他文件相关的操作。
      字节缓冲区提供了对其内容的访问(其内容作为任何非布尔基本类型的异类或同类二进制数据序列),访问要么是以 big-endian字节顺序进行,要么是以 little-endian 字节顺序进行。

      2.通道

      Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流,而且他们面向缓冲区的。
      所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
      通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。
      因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。特别是在 UNIX 模型中,底层操作系统通道是双向的。 
     

      NIO Socket编程中有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的套接字通道socketchannel注册到Selector,程序不用阻塞等待,可以并行做别的事情,当有事件发生时,Selector会通知程序,传回一组SelectionKey,程序读取这些Key,就会获得注册过的socketchannel,然后,从这个Channel中读取和处理数据。

      Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。

      eg:

    [java] view plaincopy
     
      1. import java.net.*;  
      2. import java.util.*;  
      3. import java.io.*;  
      4. import java.nio.*;  
      5. import java.nio.channels.*;  
      6. import java.nio.charset.*;  
      7.   
      8. public class NIOSocketServer{  
      9.     public static final int PORT = 8080;  
      10.     public static void main(String[] args)throws IOException{  
      11.         //NIO的通道channel中内容读取到字节缓冲区ByteBuffer时是字节方式存储的,  
      12.         //对于以字符方式读取和处理的数据必须要进行字符集编码和解码  
      13.     String encoding = System.getProperty(“file.encoding”);  
      14.     //加载字节编码集  
      15.     Charset cs = Charset.forName(encoding);  
      16.     //分配两个字节大小的字节缓冲区  
      17.     ByteBuffer buffer = ByteBuffer.allocate(16);  
      18.     SocketChannel ch = null;  
      19.     //打开服务端的套接字通道  
      20.     ServerSocketChannel ssc = ServerSocketChannel.open();  
      21.     //打开通道选择器  
      22.     Selector sel = Selector.open();  
      23.     try{  
      24.         //将服务端套接字通道连接方式调整为非阻塞模式  
      25.       ssc.configureBlocking(false);  
      26.       //将服务端套接字通道绑定到本机服务端端口  
      27.       ssc.socket().bind(new InetSocketAddress(PORT));  
      28.          //将服务端套接字通道OP_ACCEP事件注册到通道选择器上  
      29.          SelectionKey key = ssc.register(sel, SelectionKey.OP_ACCEPT);  
      30.          System.out.println(“Server on port:” + PORT);  
      31.          while(true){  
      32.                //通道选择器开始轮询通道事件  
      33.               sel.select();  
      34.               Iterator it = sel.selectedKeys().iterator();  
      35.               While(it.hasNext()){  
      36.                     //获取通道选择器事件键  
      37.                      SelectionKey skey = (SelectionKey)it.next();  
      38.                       it.remove();  
      39.                       //服务端套接字通道发送客户端连接事件,客户端套接字通道尚未连接  
      40.                        if(skey.isAcceptable()){  
      41.                                   //获取服务端套接字通道上连接的客户端套接字通道  
      42.                                    ch = ssc.accept();  
      43.                                     System.out.println(“Accepted connection from:” + ch.socket());  
      44.                                   //将客户端套接字通过连接模式调整为非阻塞模式  
      45.                                   ch.configureBlocking(false);  
      46.                                  //将客户端套接字通道OP_READ事件注册到通道选择器上  
      47.                                  ch.register(sel, SelectionKey.OP_READ);  
      48.                         }  
      49.           //客户端套接字通道已经连接  
      50.                      else{  
      51.                                    //获取创建此通道选择器事件键的套接字通道  
      52.                                    ch = (SocketChannel)skey.channel();  
      53.                                    //将客户端套接字通道数据读取到字节缓冲区中  
      54.                                    ch.read(buffer);  
      55.                                     //使用字符集解码字节缓冲区数据  
      56.                                    CharBuffer cb = cs.decode((ByteBuffer)buffer.flip());  
      57.                                    String response = cb.toString();  
      58.                                    System.out.println(“Echoing:” + response) ;  
      59.                                    //重绕字节缓冲区,继续读取客户端套接字通道数据  
      60.                                    ch.write((ByteBuffer)buffer.rewind());  
      61.                                    if(response.indexOf(“END”) != -1) ch.close();  
      62.                                    buffer.clear();  
      63.                         }  
      64.                  }  
      65.             }  
      66.     }finally{  
      67.            if(ch != null) ch.close();  
      68.            ssc.close();  
      69.            sel.close();  
      70.     }  
      71.   }  
      72. }  

       

    java NIO 和阻塞I/O的区别 

    1. 阻塞I/O通信模型 

      假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞(线程让出CPU)到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。阻塞I/O的通信模型示意图如下:

      如果你细细分析,一定会发现阻塞I/O存在一些缺点。根据阻塞I/O通信模型,两点缺点:
      1. 当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU时间

      2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。

      这种情况下非阻塞式I/O就有了它的应用前景。

    2. 
    java NIO原理及通信模型 

    Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:

      1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。 
      2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。 
      3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。 

    阅读过一些资料之后,下面贴出我理解的java NIO的工作原理图:

    (注:每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应。)

      Java NIO的服务端只需启动一个专门的线程来处理所有的 IO 事件,这种通信模型是怎么实现的呢?呵呵,我们一起来探究它的奥秘吧。java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:

    事件名 对应值
    服务端接收客户端连接事件 SelectionKey.OP_ACCEPT(16)
    客户端连接服务端事件 SelectionKey.OP_CONNECT(8)
    读事件 SelectionKey.OP_READ(1)
    写事件 SelectionKey.OP_WRITE(4)

      服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。下面是我理解的java NIO的通信模型示意图:

    参考:http://blog.csdn.net/hguisu/article/details/7453390

    http://www.cnblogs.com/zhuyp1015/archive/2012/05/31/2529079.html

    http://weixiaolu.iteye.com/blog/1479656

    http://www.tuicool.com/articles/AzmiY3

  • 相关阅读:
    leetcode(c++)计算器类
    java使用ffmpeg 实现图片、音频、视频添加水印功能(windows、linux)
    实验一密码引擎商用密码算法实现2交叉测试(选做)
    实验一密码引擎加密API研究
    微信小程序连接MQTT服务器、阿里云微消息队列MQTT版
    扫二维码进入小程序不同页面 微信小程序码生成详细方法步骤
    微信小程序之 wx.showToast延时跳转页面
    微信小程序订单页面
    精华文章
    .net core 华为云发送短信
  • 原文地址:https://www.cnblogs.com/jslee/p/3419822.html
Copyright © 2020-2023  润新知