文章目录
前言
BIO和NIO是基于Linux的后端开发的重要IO模型.
NIO(Non-blocking I/O) :
非阻塞IO, 由于还有一个AIO(Asynchronous I/O, 异步IO), 为了区分, NIO也被称为同步非阻塞IO,
现在常用的NIO, 是NIO+IO多路复用(IO Multiplexing)的结合体, epoll还是会阻塞线程, 不是真正的非阻塞IO了.
BIO(Blocking I/O) :
阻塞IO, 为了和NIO保持队形, 也被称为同步阻塞IO.
不同操作系统对于IO模型的支持程度不同,
Linux对AIO支持得并不好, 因此基于Linux的后端开发一般不用AIO, 本文也不多说它了.
一、同步/异步 和 阻塞/非阻塞
1.概念
在linux中涉及到同步和阻塞时, 一般来说, 调用方是指应用(application), 被调用方是指linux内核(kernel)
同步
调用方发起一个功能调用时, 在没有得到功能的结果之前, 该调用不会返回.
也就是调用方会等待被调用方返回功能的结果.
异步
调用方发起一个功能调用时, 没有得到功能的结果立即返回, 后续被调用方再通过回调等手段, 把功能的结果通知调用方.
也就是调用方立即得到返回,但是返回中不包含功能的结果.
在linux的AIO中, 异步的结果是通过内核的回调拿到的, 本文的NIO只涉及到同步, 没有异步.
阻塞
线程发起一个调用时, 在调用返回之前, 线程会被阻塞, 在这个状态下会交出当前CPU的使用权而暂停.
也就是调用方会等待调用结果, 调用阻塞了调用方的线程, 线程不在运行处理中.
非阻塞
线程发起一个调用时, 调用会立即返回, 避免线程被阻塞.
但是, 返回的结果只是被调用方当前状态的值, 实际使用时, 调用方需要轮询, 直到返回结果符合预期(直到数据准备好).
2.区别
2.1. 同步和异步的区别:
区别是, 调用方是否能够直接得到功能的结果
同步会一直等待, 直到得到功能的结果
异步会立即得到一个不包含结果的返回, 功能的结果要由被调用方通知.
2.2. 阻塞和非阻塞的区别:
区别是, 调用是否会立即返回, 进而是否阻塞调用方的线程, 在网络IO中, 是指在数据未准备好的情况下, 是否会阻塞线程直到数据准备好
阻塞会阻塞线程, 调用方只能等待
非阻塞由于调用立即返回, 而不阻塞线程, 调用方可以继续做别的事
2.3. 同步和阻塞区别:
区别是, 是否会阻塞调用方线程
同步不会阻塞调用方线程, 线程在运行处理中, 只是逻辑上调用未返回而已
阻塞会阻塞调用方线程, 线程不在运行处理中
2.4. 同步异步和阻塞非阻塞的区别:
区别是, 讨论点不同
同步异步讨论的是 被调用方的返回结果的通知机制
阻塞非阻塞讨论的是 调用方的线程状态
二、NIO的演变
1.NIO的诞生
1.1 前置知识
假设在Linux上运行应用, 该应用监听了某个端口, 通过网络获取外部数据.
应用(application)是运行在Linux的用户空间的,
而外部数据是在Linux的内核空间中接收到的,
外部数据先保存到网卡, 再保存到内核缓冲区, 最后进入应用缓冲区.
应用调用read方法读取外部数据, Linux的内核操作分为2步:
第一步: 等待数据就绪
第二步: 把内核缓冲区的数据复制到用户缓冲区
参照下面1.2中的图
1.2 BIO和NIO
Linux的IO模型最初的是BIO
BIO:
BIO一次read(), 触发了内核的两步操作, 全程都阻塞.
优点: 线程阻塞时, 消耗的CPU资源小
缺点: 高并发时容易耗尽线程资源, 且大量线程会引起线程切换的巨大开销, 造成响应不及时
NIO:
NIO, 靠着Linux提供的可以立即返回的read(), 在第一步中是不阻塞的, 在第二步中仍然是阻塞的.
优点: 把应用线程的read调用, 分割出了第一步和第二步, 应用线程可以做更多实时的控制
缺点: 第一步中的轮询, 大量占用CPU时间, 降低系统资源利用率, 所以一般web服务器也不使用这样的NIO
为了解决NIO轮询的缺点, 引入了IO多路复用, 用操作系统的IO多路复用, 来替代轮询
2. IO多路复用的演变
2.1 IO多路复用是什么?
IO多路复用是一种同步IO模型, 是操作系统提供的能力, 使用少量线程监听Linux的多个IO文件描述符, 也就是线程复用于多个IO请求.
如果有任意IO文件描述符就绪, 就会告知应用进行读取,
如果没有IO文件描述符就绪, 就会阻塞应用线程.
Linux中, IO多路复用的实现是select/poll/epoll
IO多路复用的作用: 线程复用于多个请求, 用较小的线程开销来支持更多的并发请求
NIO结合IO多路复用后, select/poll/epoll替代了轮询.
2.2 select/poll/epoll
Linux上的IO多路复用演变经历了3个阶段:
一.select
二.poll
三.epoll
目前使用的是epoll, 默认是epoll的水平触发模式(LT), 还有一个边缘触发模式(ET), 应用一般是用LT, 感兴趣的可以自行搜索
一.select
轮询遍历IO文件描述符, O(n)的线性时间复杂度, 因此随着IO文件描述符的增多, 性能下降
单进程能够打开的文件描述符有数量限制, 导致应用的最大连接数是1024
调用select时, 还需要文件描述符在用户空间和内核空间的拷贝, 影响性能
二.poll
同样是轮询遍历IO文件描述符, O(n)的线性时间复杂度, 因此随着IO文件描述符的增多, 性能下降
无文件描述符的数量限制
调用poll时, 还需要文件描述符在用户空间和内核空间的拷贝, 影响性能
三.epoll
解决了select和poll的全部缺点,
采用注册回调机制, 替代了轮询遍历,
无文件描述符的数量限制
利用mmap减少了文件描述符在用户空间和内核空间的拷贝
因此性能高
总结
和BIO相比, NIO主要是把"等待数据就绪"这一步拆分出来, 其本身是利用轮询来获取IO数据就绪状态.
轮询操作是同步(直接得到是否就绪的结果)的,
轮询期间, 由于调用会立即返回, 所以调用方线程是非阻塞的,
这就是NIO同步非阻塞的含义.
轮询的缺点是CPU占用大, 用IO多路复用(epoll)替代轮询后, 才使得NIO真正拥有了高并发能力.