浅谈NIO(Non-blocking I/O)

浅谈NIO浅谈NIO(Non-blocking I/O)1> BIO (Blocking I/O)2> 同步,异步,阻塞、非阻塞3> 程序在请求网络时,到底做了什么?和IO有什么关系?4> NIO原理5> NIO示例6> NIO适用场景其他内容:优化线程模型事件分发器EPoll(linux大于 2.6) 和 Poll(linux 小于2.6)read()和wri...
摘要由CSDN通过智能技术生成
三人行,必有吾师,欢迎加入星球,一起讨论技术点滴

在知识换宇宙中,取长补短,一起飞

浅谈NIO(Non-blocking I/O)

是一种同步非阻塞I/O模型

主要从以下几个方面谈一下对NIO的理解:

  1. 在NIO之前,传统IO是什么样的,有什么弊端?
  2. 同步、异步、阻塞、非阻塞的概念
  3. 程序在请求网络时,到底做了什么?和IO有什么关系?
  4. NIO的原理
  5. NIO的示例
  6. NIO的适用场景

1> BIO (Blocking I/O)

传统IO(Blocking I/O):阻塞IO,常用的是异步阻塞IO,使用场景:一般我们请求网络会开了个新的线程或者从线程池中选一个空闲的线程去执行网络请求,在发出请求后、响应到来前,这个线程一直是等待(不继续干别的)是阻塞的,直到响应到来后,回调给调用线程后,该线程才会完成,不在占用CPU。

下面看一个例子:伪代码如下:

{
   
        ExecutorService executor = Executors.newFixedThreadPool(100);
        ServerSocket serverSocket = null;

        serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(8088));//监听 8088端口
        while (true) {
   
            Socket socket = serverSocket.accept(); // 这个是阻塞的
            executor.submit(new ConnectIOHandler(socket)); //为新的连接创建新的线程
        }
}
class ConnectIOHandler implements Runnable {
   
        private Socket socket;

        public ConnectIOHandler(Socket socket) {
   
            this.socket = socket;
        }

        public void run() {
   
            if (!socket.isClosed()) {
   
                int read = socket.getInputStream().read();
                ............
                socket.getOutputStream().write(bytes);
            }
        }
}
/**
socket.accept()、socket.read()、socket.write()都是同步阻塞的,当一个连接在处理I/O时,系统是阻塞的,但cpu是释放的(瓶颈在I/O)必须使用多线程,
多线程本质:1.利用多核 2. 当I/O阻塞时,多线程使用CPU资源
注:现在多线程一般使用线程池,可以让线程的创建和回收成本相对较低,在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,不用过多的考虑系统的过载,限流等问题 ,线程池本身也是一个天然的漏斗,可以缓冲一些处理不了的连接或请求。
问题 :
最本质的问题 ,严重依赖于线程,但线程是很贵的资源,主要表现在:
1. 线程的创建和销毁成本高,在linux中,线程本身是一个进程,创建和销毁都是重量级的系统函数
2. 线程本身占用较大的内存,像Java线程栈,一般至少分配 512K~1M的空间,如果系统中的线程数过多,恐怕整个jvm的内存会被吃掉一半
3. 线程切换的成本很高,操作系统在发生线程切换时,需要保留线程的上下文,然后执行系统调用,如果线程数过多,可能执行线程切换的时间会大于线程执行的时间,这时候带来的表现往往是系统load偏高,cpu sy使用率高,导致系统几乎不可用的状态
4. 容易造成锯齿状的系统负载,因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。
*/

2> 同步,异步,阻塞、非阻塞

  • 同步:关注消息通讯机制,调用者进行调用后,会等待结果,有结果后才返回:调用者检查结果是否就绪

  • 异步:也是关注消息通讯机制,调用后立刻返回,可能没有结果。等有结果后,由被调用者通过知调用者:被调用者检查调用结果是否就绪

  • 阻塞:就等待结果时的状态,阻塞是指在调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会继续执行

  • 非阻塞:等 待结果时,调用线程不被挂起,还执行其他的事情

  • 同步阻塞、同步非阻塞、异步非阻塞,异步阻塞

  • 同步阻塞,一个线程请求网络,并等到请求结果回来

  • 同步非阻塞:一个线程请求网络后,先去执行别的,不间断的来查看网络下载结果。

  • 异步阻塞:调用线程支请求网络,一直等待请求线程的下载完成通知

  • 异步非阻塞:调用请求网络线程后,去干别的,等待来通知后在继续执行对应的逻辑,我们常用的网络请求方式

3> 程序在请求网络时,到底做了什么?和IO有什么关系?

[外链图片转存失败(img-ShI7JtrW-1562476986964)(https://raw.githubusercontent.com/winrainbow/imageRepository/master/IO.jpg)]

4> NIO原理

  1. 所有的系统的I/O都分为两个阶段:等待就绪和操作 如:读分为等待系统可读和真正的读,写分为等待网卡可以和真正的写

  2. 需要说明的是等待就绪的阻塞是不使用cpu的,在空等,而真正的读写操作的阻塞是使用cpu的,是真正干活的,而且这个过程非常快,属于memory copy 带宽通常是在1GB/s级别以上,可以理解为基本不耗时

  3. socket.read() 在BIO中,socket.read()如果TCP recvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据

    对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存中,并且返回给用户;反之直接返回0,永远不会阻塞

    AIO(Async I/O)中,不但等待就绪是非阻塞的,连数据从网卡到内存的过程也是异步的

    BIO:我要读;NIO:我可以读了 ;AIO:我读完了

  4. NIO重要特点:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)

  5. BIO模型中,之所以需要多线程,是因为进行I/O操作时,一是没有办法知道能不能写,能不能读,只能“傻等”,即使通过各种估算,算出来操作系统没有能力进行读写,也没有办法在socket.read()和socket.write()方法中返回,这两个方法无法进行有效的中断所以除了多开线程另起炉灶,没有好的办法利用CPU

  6. NIO的读写方法可以立刻返回,这就给了我们不开线程利用CPU的最好机会,如果一个连接不能读写(socket.read()或者socket.write()返回0) 我们就可以把这件事记下来,记录的方式通常是在 Selector上注册标记位,然后切换到其他就绪的连接(channel)继续进行读写

  7. NIO几个事件:读就绪、写就绪、有新连接到来

    1. 首先注册当这几个事件到来的时候所对应的处理器,然后在合适的时机告诉事件选择器:我对这个事件有兴趣;

      • 对于写操作,就是写不出去的时候对写事件有兴趣,
      • 对于读操作,就是完成连接和系统没有办法承载新读入的数据时
      • 对于accept,一般是服务器刚启动的时候,
      • 对于connect,一般是connect失败需要重连或者直接异步调用connect的时候
    2. 用一个死循环选择就绪的事件,会执行系统调用(epoll,Windows:IOCP),还会阻塞等待事件的到来,新事件到来的时候,会在selector上注册标记位,标示可读、可写、有连接到来

    select是阻塞的,无论是通过操作系统的通知,还是不停的轮询,这个函数是阻塞的,所以可以放心大胆的在一个while(true)里面调用这个方法,而不用担心cpu空转

5> NIO示例

package com.company;

import java.io.IOException;
import java.net
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值