Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端

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

作者头像
sowhat1412
发布于 2022-09-20 08:30:11
发布于 2022-09-20 08:30:11
31900
代码可运行
举报
文章被收录于专栏:sowhat1412sowhat1412
运行总次数:0
代码可运行

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

1 阻塞跟非阻塞

1.1 阻塞

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

  1. CPU把数据从磁盘读到内核缓冲区。
  2. CPU把数据从内核缓冲区拷贝到用户缓冲区。

1.2 非阻塞

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

1.3 IO多路复用

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

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

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

1.3.1 select

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

  1. select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
  2. select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但不会告诉是那个sock上有数据,只能自己遍历查找。
  3. select 只能监视1024个链接。
  4. select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现这个sock不用,要收回,这个select 不支持的。
1.3.2 poll

poll 修复了 select 的很多问题。

  1. poll 去掉了1024个链接的限制。
  2. poll 从设计上来说不再修改传入数组。

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

1.3.3 epoll

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

  1. epoll 现在是线程安全的。
  2. epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。
  3. epoll 内核态管理了各种IO文件描述符, 以前用户态发送所有文件描述符到内核态,然后内核态负责筛选返回可用数组,现在epoll模式下所有文件描述符在内核态有存,查询时不用传文件描述符进去了。
1.3.4 三者对比

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

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

1.4 异步IO

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

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

2 同步跟异步

2.1 同步

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

  1. 同步阻塞:此时一个线程维护一个连接,该线程完成数据到读写跟处理到全部过程,数据读写时时线程是被阻塞的。
  2. 同步非阻塞:非阻塞的意思是用户线程发出读请求后,读请求不会阻塞当前用户线程,不过用户线程还是要不断的去主动判断数据是否准备OK了。此时还是会阻塞等待内核复制数据到用户进程。他与同步BIO区别是使用一个连接全程等待

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

同步非阻塞

2.2 异步

对于异步来说,用户进行读或者写后,将立刻返回,由内核去完成数据读取以及拷贝工作,完成后通知用户,并执行回调函数(用户提供的callback),此时数据已从内核拷贝到用户空间,用户线程只需要对数据进行处理即可,不需要关注读写,用户不需要等待内核对数据的复制操作,用户在得到通知时数据已经被复制到用户空间。我们以如下的真实异步非阻塞为例。

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

2.3 同步跟异步对比

同步关注的消息通信机制synchronous communication,在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。

异步关注消息通信机制asynchronous communication,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

3 Java IO

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

  1. BIO:同步阻塞IO。
  2. NIO:同步非阻塞IO。
  3. AIO:异步非阻塞IO。

3.1 BIO

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

BIO BIO特点

  1. 使用一个独立的线程维护一个socket连接,随着连接数量的增多,对虚拟机造成一定压力。
  2. 使用流来读取数据,流是阻塞的,当没有可读/可写数据时,线程等待,会造成资源的浪费。
3.1.1 BIO 样例

常量:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Constant {
    public static final String HOST = "127.0.0.1";
    public static final int PORT = 8080;
}

主类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ClientMain {
    public static void main(String[] args) {
        //开启服务
        System.out.println("开启服务,监听端口:" + Constant.PORT);
        new Thread(new ServerThread()).start();
        //建立一个socket客户端,发起请求
        System.out.println("客户端,请求连接,并发送数据");
        try {
            Socket socket = new Socket(Constant.HOST,Constant.PORT);
            //开启新的线程处理socket连接
            new Thread(new ClientProcessThread(socket)).start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

服务端监听线程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 开启服务监听线程,当收到连接请求后,开启新的线程进行处理
public class ServerThread implements Runnable{
    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(Constant.PORT);
            while (true){
                Socket socket = serverSocket.accept();
                new Thread(new ServerProcessThread(socket)).start();
                //开启新的线程进行连接请求的处理
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务端处理线程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.*;
import java.net.Socket;
/**
 * 服务端收到连接请求后,处理请求的线程,阻塞式IO
 */
public class ServerProcessThread implements Runnable {
    private Socket socket;
    public ServerProcessThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        //获取客户端的数据,并写回
        //等待响应
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = "";
            String requestStr = "";
            System.out.println("来自客户端的数据:"); // 读取客户端数据
            while((line = bufferedReader.readLine()) != null){
                requestStr += line;
                System.out.println(line);
            }
            //  从服务端发给客户端数据
            Writer writer = new OutputStreamWriter(socket.getOutputStream());
            writer.write("data from server " + requestStr + "\r\n");
            writer.flush();
            writer.close();
            bufferedReader.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 维护客户端socket连接的线程,阻塞式IO
 */
public class ClientProcessThread implements Runnable {
    private Socket socket;
    public ClientProcessThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        //写数据,等待响应,输出响应
        String requestStr = "data from client \r\n";
        try {
            Writer writer = new OutputStreamWriter(socket.getOutputStream());
            writer.write(requestStr);
            writer.flush();
            socket.shutdownOutput();
            //等待响应
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            System.out.println("来自服务端的响应:");
            while((line = bufferedReader.readLine()) != null){
                System.out.println(line);
            }
            writer.close();
            bufferedReader.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

3.2 NIO

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

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

  1. 不必对每个连接分别创建线程。
  2. 数据读写非阻塞。

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

  1. selector:Selector 允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。要使用Selector,得向Selector注册Channel,然后调用他的select方法,这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子入有新连接接进来,数据接收等。
  2. Channel:基本上所有的IO在NIO中都从一个Channel开始。Channel有点像流,数据可以从channel到buffer,也可以从buffer到channel。
  3. Buffer:缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松的使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变换情况,Channel提供从文件,网络读取数据的渠道,但是读取或者写入的数据都必须经由Buffer。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ByteBuffer
CharBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer

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

Netty通讯模式

3.3 AIO

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

4 参考

  1. IO说:https://blog.csdn.net/u013177446/article/details/65936341
  2. 爆赞TCP讲解:https://b23.tv/tMxwQV
  3. 通俗说IO:https://www.cnblogs.com/LBSer/p/4622749.html
  4. 小仙IO:https://t.1yb.co/iEAW

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 sowhat1412 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java NIO
了解java的NIO,需要先了解同步异步以及阻塞非阻塞的概念,同步/异步,阻塞/非阻塞 NIO就是采用的同步非阻塞这种组合方式。或简单一点,采用的是IO复用的策略,可以使用一个线程管理多个IO连接。
欠扁的小篮子
2018/04/10
1.2K0
Java NIO
关于Java的BIO,NIO和AIO的演进
Java里面的IO模型种类较多,主要包括BIO,NIO和AIO,每个IO模型都有不一样的地方,那么这些IO模型是如何演变呢,底层的原理又是怎样的呢? 本文我们就来聊聊。
我是攻城师
2018/12/17
1K0
IO多路复用机制详解
(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
lyb-geek
2018/07/26
3.5K0
IO多路复用机制详解
一文说清BIO、NIO、AIO不同IO模型演进之路
Netty作为高性能的网络通信框架,它是IO模型演变过程中的产物。Netty以Java NIO为基础,是一种基于异步事件驱动的网络通信应用框架,Netty用以快速开发高性能、高可靠的网络服务器和客户端程序,很多开源框架都选择Netty作为其网络通信模块。本文主要通过分析IO模型的优化演进之路,比较不同IO模型的异同,让大家对于Java IO模型有着更加深刻的理解,我想这也是Netty如何实现高性能网络通信理解的重要基础。话不多说,我们赶紧发车了。
慕枫技术笔记
2023/03/20
6000
一文说清BIO、NIO、AIO不同IO模型演进之路
BIO、NIO、IO多路复用模型的演进&Java NIO 网络编程
上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题,从而造成服务端崩溃的现象。怎么解决这一问题呢?优化呗,所以后面就有了NIO、AIO、IO多路复用。本文将对这几个模型详细说明并基于 Java 编写 NIO。
王二蛋
2024/01/18
7720
Java核心(五)深入理解BIO、NIO、AIO
导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别;BIO、NIO、AIO 的区别;理解和实现 NIO 操作 Socket 时的多路复用;同时掌握 IO 最底层最核心的操作技巧。
磊哥
2018/12/24
6920
BIO 和 NIO 的区别和原理
BIO(Blocking IO) 又称同步阻塞IO,一个客户端由一个线程来进行处理
BUG弄潮儿
2023/02/24
4290
BIO 和 NIO 的区别和原理
100% 弄明白Java NIO
Java NIO是为了解决高并发请求提出的设计模型,是基于IO多路复用设计出来的。底层又依赖于操作系统的支持(select、poll、epoll)。
公众号 云舒编程
2024/01/25
2200
网络 IO 模型简单介绍
当用户线程调用了 read 系统调用,内核(kernel)就开始了 IO 的第一个阶段:准备数据。很多时候,数据在一开始还没有到达(比如,还没有收到一个完整的Socket数据包),这个时候 kernel 就要等待足够的数据到来。
JMCui
2020/12/18
4350
网络 IO 模型简单介绍
认识下IO五兄弟,BIO、NIO、AIO、IO多路复用、 信号驱动IO
在上一篇文章中,我们了解了操作系统中内核程序和用户程序之间的区别和联系,还提到了内核空间和用户空间,当我们需要读取一条数据的时候,首先需要发请求告诉内核,我需要什么数据,等内核准备好数据之后 , 再从内核空间拷贝到用户空间 注意加粗的部分,这两个阶段至关重要
用户4283147
2022/10/27
9290
Netty序章之BIO NIO AIO演变
Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠的网络服务器和客户端程序。Netty简化了网络程序的开发,是很多框架和公司都在使用的技术。更是面试的加分项。Netty并非横空出世,它是在BIO,NIO,AIO演变中的产物,是一种NIO框架。而BIO,NIO,AIO更是笔试中要考,面试中要问的技术。也是一个很好的加分项,加分就是加工资,你还在等什么?本章带你细细品味三者的不同! 流程图:
用户1212940
2022/04/13
5300
Netty序章之BIO NIO AIO演变
【JAVA】NIO 如何实现多路复用?
IO 一直是软件开发中的核心部分之一,伴随着海量数据增长和分布式系统的发展,IO 扩展能力愈发重要。幸运的是,Java 平台 IO 机制经过不断完善,虽然在某些方面仍有不足,但已经在实践中证明了其构建高扩展性应用的能力。
sidiot
2023/08/31
7820
【JAVA】NIO 如何实现多路复用?
NIO/IO多路复用
NIO 是一种同步非阻塞模型(Non-blocking IO),也是 IO 多路复用的基础。在了解 NIO 之前我们先回顾一下我们传统 IO 的相关知识。
shysh95
2020/06/03
1.9K0
了解NIO和BIO
1.linux系统中一切皆文件当有文件 当有一个请求过来的時候就通过3次握手就会和内核创建连接关系,此时Tomcat中的启动的的端口监控就会检测到内核中的文件标识符 fd 此时由linux提供的API socket就会应用程序通过accept()去监控到对应的文件,然后启用线程read(fd)去获取socket的文件流。应用程序读根据文件标识符去读取文件流的过程也就是IO
袁新栋-jeff.yuan
2020/08/26
4560
BIO与NIO与多路复用
首先需要了解下什么是IO,IO就是读入/写出数据的过程,和等待读入/写出数据的过程。
Lvshen
2022/05/05
3090
BIO与NIO与多路复用
Java IO: BIO, NIO, AIO
BIO, NIO, AIO,本身的描述都是在Java语言的基础上的。 而描述IO,我们需要从三个层面:
九州暮云
2019/08/21
6910
Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式
Java I/O模型 同步 vs. 异步 同步I/O 每个请求必须逐个地被处理,一个请求的处理会导致整个流程的暂时等待,这些事件无法并发地执行。用户线程发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行。 异步I/O 多个请求可以并发地执行,一个请求或者任务的执行不会导致整个流程的暂时等待。用户线程发起I/O请求后仍然继续执行,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数。 阻塞 vs. 非阻塞 阻塞 某个请求发出后,由于该请求操作需要的条件不满足,请求操作一直阻塞,
Jason Guo
2018/06/19
7070
10分钟看懂, Java NIO 底层原理
很多的小伙伴,被java IO 模型,搞得有点儿晕,一会儿是4种模型,一会儿又变成了5种模型。
Java识堂
2020/11/03
8140
搞懂I/O多路复用及其技术
高性能是每个程序员的追求,无论写一行代码还是做一个系统,都希望能够达到高性能的效果。高性能架构设计主要集中在两方面:
BUG弄潮儿
2021/01/05
6400
Java NIO、BIO、 AIO 与 同步、阻塞、非阻塞、异步IO 简析
我相信大部分人看到这些名词,都是一头雾水的,如果你去搜索引擎搜索,那么恭喜你,你又会被各种文章中的高大上的名词搞得云里雾里。那么,我们应该怎么理清这么名词之间的关系呢?
卢衍飞
2023/02/16
3980
相关推荐
Java NIO
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档