nio学习-多路复用

概述

文件将会介绍关于多路复用的起源, 以及几种实现的历史, 文章部分表述来源已标注.

多路复用(I/O multiplexing)的动机

​ 还记得我们开始写 java IO 编程的时候如何去处理网络请求的吗?

  • 多进程并发模型 (每进来一个新的I/O流会分配一个新的进程管理。)

  • I/O多路复用 (单个线程,通过记录跟踪每个I/O流(sock)的状态,来同时管理多个I/O流 。)

    我们知道接收一个网络请求, 经过三次握手后 , 先是 connect 再是 accept , 最后业务逻辑处理, 这中间要是给一个线程服务, 有可能业务逻辑耗时比较长导致连接的数量就受到了限制, 所以像 nginx redis 使用到了多路复用的技术来解决处理网络请求的问题 .

    我们要知道线程是很"贵"的 , 主要体现在 :

    • 线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数。
    • 线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半。
    • 线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态。
    • 容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。

多路复用是怎么样的

可以看下面这张图,

1297993-20210809150141708-1919898092.jpg

nginx使用epoll接收请求的过程是怎样的”, 多看看这个图就了解了。提醒下,ngnix会有很多链接进来, epoll会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。

来源 : https://www.zhihu.com/question/32163005/answer/55772739

linux 中多路复用的实现有三种 : epoll, poll, select 这三种, 其中 epoll 的效果最好.

epoll poll select 三者历史

下面表述来自 : https://www.zhihu.com/question/32163005/answer/55772739 , 非原创

作者:罗志宇

链接:https://www.zhihu.com/question/32163005/answer/55772739

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

多路复用之select

I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。

select 被实现以后,很快就暴露出了很多问题。

  • select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
  • select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。
  • select 只能监视1024个链接, 这个跟草榴没啥关系哦,linux 定义在头文件中的,参见FD_SETSIZE。
  • select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现,尼玛,这个sock不用,要收回。对不起,这个select 不支持的,如果你丧心病狂的竟然关掉这个sock, select的标准行为是。。呃。。不可预测的, 这个可是写在文档中的哦. 霸不霸气

“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”

多路复用之poll

14年以后(1997年)一帮人又实现了poll, poll 修复了select的很多问题,比如

  • poll 去掉了1024个链接的限制,于是要多少链接呢, 主人你开心就好。
  • poll 从设计上来说,不再修改传入数组,不过这个要看你的平台了,所以行走江湖,还是小心为妙。

其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。

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

多路复用之epoll

于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.

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

  • epoll 现在是线程安全的。
  • epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。

epoll 当年的patch,现在还在,下面链接可以看得到:
/dev/epoll Home Page

贴一张霸气的图,看看当年神一样的性能(测试代码都是死链了, 如果有人可以刨坟找出来,可以研究下细节怎么测的).

imgimg

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

可是epoll 有个致命的缺点。只有linux支持。比如BSD上面对应的实现是kqueue . 而ngnix 的设计原则里面, 它会使用目标平台上面最高效的I/O多路复用模型咯,所以才会有这个设置。一般情况下,如果可能的话,尽量都用epoll/kqueue吧。nginx 相关的文档

1297993-20210809151203709-946705414.png

小对比

1297993-20210809152223481-368272469.png

总结

参考资料中有一篇文章 , 可以看完相关源码进行进阶学习 !!

参考资料

原文地址:https://www.cnblogs.com/Benjious/p/15208292.html