承接上文的操作系统,关于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 样例

常量:

public class Constant {public static final String HOST = "127.0.0.1";public static final int PORT = 8080;
}

主类:

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();}}
}

服务端监听线程:

// 开启服务监听线程,当收到连接请求后,开启新的线程进行处理
public class ServerThread implements Runnable{@Overridepublic 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();}}
}

服务端处理线程:

import java.io.*;
import java.net.Socket;
/*** 服务端收到连接请求后,处理请求的线程,阻塞式IO*/
public class ServerProcessThread implements Runnable {private Socket socket;public ServerProcessThread(Socket socket){this.socket = socket;}@Overridepublic 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();}}
}

客户端:

/*** 维护客户端socket连接的线程,阻塞式IO*/
public class ClientProcessThread implements Runnable {private Socket socket;public ClientProcessThread(Socket socket){this.socket = socket;}@Overridepublic 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的实现:

FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel

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

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

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

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

  1. bio阻塞的缺点_java 中的 BIO/NIO/AIO 详解

    java 的 IO 演进之路 我们在前面学习了 linux 的 5 种 I/O 模型详解 下面我们一起来学习下如何使用 java 实现 BIO/NIO/AIO 这 3 种不同的网络 IO 模型编程. ...

  2. 【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)...

    网上有很多讲同步/异步/阻塞/非阻塞/BIO/NIO/AIO的文章,但是都没有达到我的心里预期,于是自己写一篇出来. 常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTTP接口拿到详情数 ...

  3. 迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章

    来源:编程新说 网上有很多讲同步/异步/阻塞/非阻塞/BIO/NIO/AIO的文章,但是都没有达到我的心里预期,于是自己写一篇出来. 常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTT ...

  4. 确定不来了解一下什么是 BIO NIO AIO 阻塞 非阻塞 同步 异步?

    本文内容涉及同步与异步, 阻塞与非阻塞, BIO.NIO.AIO等概念, 这块内容本身比较复杂, 很难用三言两语说明白. 而书上的定义更不容易理解是什么意思. 下面跟着我一起解开它们神秘的面纱. BI ...

  5. AIO+BIO+NIO+同步+异步+阻塞+非阻塞

    一句话: AIO是NIO的升级版,NIO是BIO的升级版[1] 所以其实是用AIO IO名称 JDK版本 数据类型 IO类型 IO   流   BIO(偶尔也叫做OIO) <1.4   同步阻塞 ...

  6. cpu 被挂起和阻塞_迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章...

    网上有很多讲同步/异步/阻塞/非阻塞/BIO/NIO/AIO的文章,但是都没有达到我的心里预期,于是自己写一篇出来. 常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTTP接口拿到详情数 ...

  7. NIO详解(二): BIO 浅谈 同步 异步与阻塞 非阻塞

    在我们了解Java NIO/BIO的网络通信之前,我们先了解一下常用的阻塞/非阻塞模型以及同步/异步的概念 一.阻塞和非阻塞 从简单的开始,我们以经典的读取文件的模型举例.(对操作系统而言,所有的输入 ...

  8. 面试必会系列 - 5.1 网络BIO、NIO、epoll,同步/异步模型、阻塞/非阻塞模型,你能分清吗?

    本文已收录至 Github(MD-Notes),若博客中图片模糊或打不开,可以来我的 Github 仓库,包含了完整图文:https://github.com/HanquanHq/MD-Notes,涵 ...

  9. 迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)...

    常规的误区 假设有一个展示用户详情的需求,分两步,先调用一个HTTP接口拿到详情数据,然后使用适合的视图展示详情数据. 如果网速很慢,代码发起一个HTTP请求后,就卡住不动了,直到十几秒后才拿到HTT ...

  10. 同步异步阻塞非阻塞杂记

    版权声明:本文可能为博主原创文章,若标明出处可随便转载. https://blog.csdn.net/Jailman/article/details/78498458 gevent实现的协程是同步非阻 ...

最新文章

  1. nodejs mysql 创建连接池
  2. 微信小程序获取用户的 OpenId (附前端代码)
  3. 【渝粤题库】广东开放大学 跨文化商务沟通 形成性考核
  4. C排序算法:(一)直接排序
  5. mysql注入5.0以上_[Injection]对MYSQL 5.0服务器以上版本注入
  6. 4009-基于邻接表的边的删除(C++,附思路)
  7. Hadoop1.0.4单节点StandAlone及Pseudo-Distributed部署方法
  8. 程序、进程与线程的关系
  9. python密码传参有特殊字符如何解决_am start的总结,-d参数的总结,以及python中传递内容包含中文及特殊字符的解决方案...
  10. IIS的URL重写(url地址映射)
  11. SpringBoot 微信H5支付
  12. Collecting package metadata (current_repodata.json): failed(解决方案)
  13. 查看与设置华为路由器的版本信息,名称、时钟、登录提示
  14. Python中文社区官方群组
  15. Java(二十二) -- 生产者消费者模式
  16. Java的时间格式化
  17. AndroidStudio输入生日计算星座
  18. Android Shape渐变色
  19. 数据结构大作业-DBLP科学文献管理系统(一) XML解析,文件哈希,C++线程
  20. 上学最恐怖的事在于上课前点名签到,尤其这个签到脚本更恐怖。

热门文章

  1. 如何在命令行上创建符合特定规范的密码?
  2. Linux工业嵌入式应用
  3. 怎样学会单片机?先学什么,怎么入门?
  4. mysql udate 充值_分享下一个mysql的充值记录系统
  5. 【Java】LeetCode 876 获取链表中间节点
  6. 数论基础--矩阵快速幂 及其例题
  7. 聚类分析在用户行为中的实例_序列模式挖掘在用户行为分析中的应用
  8. Python基础4_列表,元祖
  9. npm scripts的生命周期管理
  10. 9月21日科技联播:报告显示网约车案发率低于传统出租车;刘强东性侵案已移交美国检方...