阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一文搞定

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一文搞定相关的知识,希望对你有一定的参考价值。

参考技术A

关于IO会涉及到阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO等几个知识点。知识点虽然不难但平常经常容易搞混,特此Mark下,与君共勉。

阻塞IO情况下,当用户调用 read 后,用户线程会被阻塞,等内核数据准备好并且数据从内核缓冲区拷贝到用户态缓存区后 read 才会返回。可以看到是阻塞的两个部分。

非阻塞IO发出read请求后发现数据没准备好,会继续往下执行,此时应用程序会不断轮询polling内核询问数据是否准备好,当数据没有准备好时,内核立即返回EWOULDBLOCK错误。直到数据被拷贝到应用程序缓冲区,read请求才获取到结果。并且你要注意!这里最后一次 read 调用获取数据的过程,是一个同步的过程,是需要等待的过程。这里的同步指的是 内核态的数据拷贝到用户程序的缓存区这个过程

非阻塞情况下无可用数据时,应用程序每次轮询内核看数据是否准备好了也耗费CPU,能否不让它轮询,当内核缓冲区数据准备好了,以事件通知当机制告知应用进程数据准备好了呢?应用进程在没有收到数据准备好的事件通知信号时可以忙写其他的工作。此时 IO多路复用 就派上用场了。

IO多路复用中文比较让人头大,IO多路复用的原文叫 I/O multiplexing,这里的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流. 发明它的目的是尽量多的提高服务器的吞吐能力。实现一个线程监控多个IO请求,哪个IO有请求就把数据从内核拷贝到进程缓冲区,拷贝期间是阻塞的!现在已经可以通过采用mmap地址映射的方法,达到内存共享效果,避免真复制,提高效率。

select、poll、epoll 都是I/O多路复用的具体的实现。

select是第一版IO复用,提出后暴漏了很多问题。

poll 修复了 select 的很多问题。

但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。

epoll 可以说是 I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:

横轴 Dead connections 是链接数的意思,叫这个名字只是它的测试工具叫deadcon。纵轴是每秒处理请求的数量,可看到epoll每秒处理请求的数量基本不会随着链接变多而下降的。poll 和/dev/poll 就很惨了。但 epoll 有个致命的缺点是只有 linux 支持。

比如平常nginx为何可以支持4W的QPS是因为它会使用目标平台上面最高效的I/O多路复用模型。

然后你会发现上面的提到过的操作都不是真正的异步,因为两个阶段总要等待会儿!而真正的异步 I/O 是内核数据准备好和数据从内核态拷贝到用户态这两个过程都不用等待。

很庆幸,Linux给我们准备了 aio_read aio_write 函数实现真实的异步,当用户发起aio_read请求后就会自动返回。内核会自动将数据从内核缓冲区拷贝到用户进程空间,应用进程啥都不用管。

我强力推荐C++后端开发免费学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

同步跟异步的区别在于 数据从内核空间拷贝到用户空间是否由用户线程完成 ,这里又分为同步阻塞跟同步非阻塞两种。

我们以同步非阻塞为例,如下可看到,在将数据从内核拷贝到用户空间这一过程,是由用户线程阻塞完成的。

可发现,用户在调用之后会立即返回,由内核完成数据的拷贝工作,并通知用户线程,进行回调。

在Java中,我们使用socket进行网络通信,IO主要有三种模式,主要看 内核支持 哪些。

同步阻塞IO ,每个客户端的Socket连接请求,服务端都会对应有个处理线程与之对应,对于没有分配到处理线程的连接就会被阻塞或者拒绝。相当于是 一个连接一个线程 。

BIO特点

常量:

主类:

服务端监听线程:

服务端处理线程:

客户端:

同步非阻塞IO之NIO :服务器端保存一个Socket连接列表,然后对这个列表进行轮询,如果发现某个Socket端口上有数据可读时说明读就绪,则调用该socket连接的相应读操作。如果发现某个 Socket端口上有数据可写时说明写就绪,则调用该socket连接的相应写操作。如果某个端口的Socket连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到了很大提高,在进行IO操作请求时候再用个线程去处理,是 一个请求一个线程 。Java中使用Selector、Channel、Buffer来实现上述效果。

每个线程中包含一个 Selector 对象,它相当于一个通道管理器,可以实现在一个线程中处理多个通道的目的,减少线程的创建数量。远程连接对应一个channel,数据的读写通过buffer均在同一个 channel 中完成,并且数据的读写是非阻塞的。通道创建后需要注册在 selector 中,同时需要为该通道注册感兴趣事件(客户端连接服务端事件、服务端接收客户端连接事件、读事件、写事件), selector 线程需要采用 轮训 的方式调用 selector 的 select 函数,直到所有注册通道中有兴趣的事件发生,则返回,否则一直阻塞。而后循环处理所有就绪的感兴趣事件。以上步骤解决BIO的两个瓶颈:

下面对以下三个概念做一个简单介绍,Java NIO由以下三个核心部分组成:

channel和buffer有好几种类型。下面是Java NIO中的一些主要channel的实现:

正如你所看到的,这些通道涵盖了UDP和TCP网络IO,以及文件IO。以下是Java NIO里关键的buffer实现:

在微服务阶段,一个请求可能涉及到多个不同服务之间的跨服务器调用,如果你想实现高性能的PRC框架来进行数据传输,那就可以基于Java NIO做个支持长连接、自定义协议、高并发的框架,比如Netty。Netty本身就是一个基于NIO的网络框架, 封装了Java NIO那些复杂的底层细节,给你提供简单好用的抽象概念来编程。比如Dubbo底层就是用的Netty。

AIO是异步非阻塞IO,相比NIO更进一步,进程读取数据时只负责发送跟接收指令,数据的准备工作完全由操作系统来处理。

推荐一个零声教育C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,mysql,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 立即学习

原文:阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端

详解 同步异步阻塞非阻塞 与 BIO NIO AIO区别多路复用

(目录)


一、IO 介绍

IO的全称其实是:Input/Output的缩写

传统的 IO 大致可以分为 4种类型


那内核是如何进行IO交互的呢?

对应抽象到java的socket代码简单示例如下:

public class SocketServer 
  public static void main(String[] args) throws Exception 
    // 监听指定的端口
    int port = 8080;
    ServerSocket server = new ServerSocket(port);
    // server将一直等待连接的到来
    Socket socket = server.accept();
    // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
    InputStream inputStream = socket.getInputStream();
    byte[] bytes = new byte[1024];
    int len;
    while ((len = inputStream.read(bytes)) != -1) 
      //获取数据进行处理
      String message = new String(bytes, 0, len,"UTF-8");
    
    // 省略……
  


二、同步与异步

同步 和 异步 指的是:

假设我们的执行流程中:依次是方法一和方法二

同步与异步是从多个线程之间的协调来实现效率差异


三、阻塞与非阻塞

阻塞与非阻塞 主要是从 CPU 的消耗上来说

虽然表面上看 非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估

阻塞与非阻塞关注的是线程是否在原地等待


四、BIO NIO AIO

1. BIO


2. NIO

NIO也叫Non-Blocking IO 是同步非阻塞的IO模型

Java中的NIO 是new IO的意思。其实是NIO加上IO多路复用技术普通的NIO是线程轮询查看一个IO缓冲区是否就绪,而Java中的new IO指的是线程轮询地去查看一堆IO缓冲区中哪些就绪,这是一种IO多路复用的思想。IO多路复用模型中,将检查IO数据是否就绪的任务,交给系统级别的select或epoll模型,由系统进行监控,减轻用户线程负担。


3. AIO

AIO是真正意义上的异步非阻塞IO模型

AIO可以做到真正的异步的操作,但实现起来比较复杂,支持纯异步IO的操作系统非常少,目前也就windows是IOCP技术实现了,而在Linux上,底层还是是使用的epoll实现的。


总结:

举个例子:

哪种方式更有效率呢?是不是一目了然呢?



五、Socket 和 NIO 的多路复用

1. 传统的 Socket 实现

代码如下:

int port = 4343; //端口号
// Socket 服务器端(简单的发送信息)
Thread sThread = new Thread(new Runnable() 
    @Override
    public void run() 
        try 
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) 
                // 等待连接
                Socket socket = serverSocket.accept();
                Thread sHandlerThread = new Thread(new Runnable() 
                    @Override
                    public void run() 
                        try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) 
                            printWriter.println("hello world!");
                            printWriter.flush();
                         catch (IOException e) 
                            e.printStackTrace();
                        
                    
                );
                sHandlerThread.start();
            
         catch (IOException e) 
            e.printStackTrace();
        
    
);
sThread.start();

// Socket 客户端(接收信息并打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) 
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));
 catch (UnknownHostException e) 
    e.printStackTrace();
 catch (IOException e) 
    e.printStackTrace();

  • 调用 accept 方法,阻塞等待客户端连接;
  • 利用 Socket 模拟了一个简单的客户端,只进行连接、读取和打印;

以上的流程,如下图:


2. NIO 多路复用

介于以上高并发的问题,NIO 的多路复用功能就显得意义非凡了

// NIO 多路复用
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4,
        60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
threadPool.execute(new Runnable() 
    @Override
    public void run() 
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) 
            serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) 
                selector.select(); // 阻塞等待就绪的Channel
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) 
                    SelectionKey key = iterator.next();
                    try (SocketChannel channel = ((ServerSocketChannel) key.channel()).accept()) 
                        channel.write(Charset.defaultCharset().encode("你好,世界"));
                    
                    iterator.remove();
                
            
         catch (IOException e) 
            e.printStackTrace();
        
    
);

// Socket 客户端(接收信息并打印)
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) 
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -> System.out.println("NIO 客户端:" + s));
 catch (IOException e) 
    e.printStackTrace();

下面的图,可以有效的说明 NIO 复用的流程:

就这样 NIO 的多路复用就大大提升了服务器端响应高并发的能力。


以上是关于阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一文搞定的主要内容,如果未能解决你的问题,请参考以下文章

Java IO------------------BIO(同步阻塞)NIO1.0(多路复用)NIO2.0(AIO,非阻塞)

阻塞非阻塞多路复用同步异步BIONIOAIO 一锅端

Java BIO,NIO,AIO

IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别

IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别

(转)IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别