带你深入了解IO多路复用技术

简介: IO多路复用技术是操作系统级的技术,也就是我们常说的底层原理。好多框架,中间件都是使用了IO多路复用技术,才使其具备更高的性能,比如我们经常使用的Redis、Nginx和我们耳熟能详的高性能通信框架Netty。本篇文章我们一起探究一下IO多路复用技术。

1 IO的理解

I - Input O - output

这里的IO我们常常指网络的IO,也就是指套接字Socket通信。网络传输的本质也就是输入输出,所有才有了IO之称。

Socket 的中文翻译为插口。双方要进行网络通信之前,各自需要创建一个 Socket,这相当于客户端和服务器都打开一个插口,双方读取和发送数据的时候,都通过这个插口建立数据通道,进行网络通信。

2 常见的IO模型

  • 同步阻塞IO(Blocking IO) 就是我们常说的BIO
  • 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
  • IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。这个就是我们Java常说的NIO,也是Netty的核心。
  • 信号驱动IO(signal driven IO)
  • 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。这就是我们Java常说的AIO。

同步: 前四种都属于同步IO,同步是指用户线程发起IO请求后,需要等待或者轮询内核IO操作完成后,才能继续执行。

异步: 异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

阻塞: 阻塞是指IO操作需要彻底完成后才返回到用户。

非阻塞: 非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

3 多路复用技术

多路复用技术是建立在内核提供的多路分离函数基础之上的,使用多路复用技术可以避免同步非阻塞IO模型中轮询等待等问题。

多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

谈及IO多路复用技术不得不介绍一下IO模型的发展:

最早期一个进程只能处理一个网络连接,后来演化成多进程,多线程处理,即每加入一个连接,操作系统就必须创建一个进程或者一个线程去处理IO,这样操作系统的压力无疑是巨大的。

IO多路复用技术就是为解决上述问题所产生的,使用IO多路复用技术可以使我们只需要一个进程就能处理多个网络连接。多个请求复用一个进程,这也是IO多路复用名称的由来。select, poll, epoll就是操作系统为我们提供的可以通过一个进程,获取多个事件的函数。

4 select, poll, epoll

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

  • select 跨平台 1983年

    select 检测到的连接是有上限的,通常是1024,select 不是线程安全的。

  • poll (linux支持) 1997年

    poll修复了select的许多问题,去掉了连接上限,但是poll仍然不是线程安全的。

  • epoll (linux支持,效率更高)2002年

    epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题,并且epoll 是线程安全的,epoll 不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据。

select

select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合中,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符,这也就是select方式有检测连接上限最底层的原因。

poll

poll 不再使用 BitsMap 来存储所关注的文件描述符,它改用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

poll 和 select 并没有太大的本质区别,都是使用线性结构存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合。这样的方式,在大规模请求下,性能会急剧下降。

epoll

epoll 是为解决 select/poll 带来的问题而产生的,也是目前最主流的。

首先,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,通过对黑红树进行操作,这样就不需要像 select/poll 每次操作时都传入整个 socket 集合,只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。

其次, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

5 总结

仔细思考一下,为什么各个大厂会对数据结构与算法情有独钟呢?就如同IO多路复用这样的底层技术,在它的升级演进过程中数据结构都发挥了巨大的作用,高效的红黑树,能够直接将时间复杂度降低到 O(logn),还有就是一些设计思想的融入,如事件驱动等,可以极大的提高检测速度,就是这些让epoll的性能,大大的超过了select/poll。我们去学习这些技术,不光要去知道它的实现原理,更重要的是去思考它演进的过程,进而去推动技术的创新与发展。

目录
相关文章
|
1月前
|
存储 监控 Linux
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
152 0
|
1月前
|
数据采集 异构计算
LabVIEW编程LabVIEW开发高级数据采集技术 操作数字IO 例程与相关资料
LabVIEW编程LabVIEW开发高级数据采集技术 操作数字IO 例程与相关资料
56 22
|
7天前
|
存储 运维 算法
Python文件处理(IO 技术)-2
Python文件处理(IO 技术)
|
1月前
|
设计模式 缓存 Java
【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战开发指南( 实现可伸缩IO专题)— 上
【Java技术专题】「入门到精通系列教程」深入探索Java特性中并发编程体系的原理和实战开发指南( 实现可伸缩IO专题)— 上
54 0
|
2天前
|
Linux C++
c++高级篇(三) ——Linux下IO多路复用之poll模型
c++高级篇(三) ——Linux下IO多路复用之poll模型
|
2天前
|
缓存 监控 网络协议
c++高级篇(二) ——Linux下IO多路复用之select模型
c++高级篇(二) ——Linux下IO多路复用之select模型
|
7天前
|
存储 编解码 Linux
Python文件处理(IO 技术)-1
Python文件处理(IO 技术)
|
21天前
|
存储 Java API
Java语言IO(输入/输出)编程技术深度解析
Java语言IO(输入/输出)编程技术深度解析
251 1
|
27天前
|
存储 Java
Java IO流:深入解析与技术应用
Java IO流:深入解析与技术应用
263 1
|
1月前
|
存储 监控 网络协议
IO多路复用
IO多路复用