Java NIO分析(2): I/O多路复用历史杂谈
前面Java-NIO分析-1-Unix网络模型讲过5种经典I/O模型, 现代企业的场景一般是高并发高流量,长连接, 假设硬件资源充足,如何提高应用单机能接受链接的上限? 先讲段历史
UNIX的出现
20世纪60年代中期, 那会儿还是批处理任务的天下,也就是有一堆job一个个顺序做, 一个做完了才做下一个. 举个栗子, 你没法边听音乐边写博客,也没法边下边播x老师的电影. 随后分时这革命性的理念提出来了, 每个job只允许占有一小段CPU时间片执行代码, 假如cpu处理的够快,看起来就像是一堆job并行一样。
分时理念无疑极大地减少了写代码和获取代码执行结果的时间,到了70年代,有人提出要发明一种更好的,多用户的, 分时的环境来执行大多数的共同任务,比如 执行需要大量CPU计算的程序,大量的磁盘访问等等, 这个环境后来就发展成了Unix
当时,程序阻塞的条件是:
- 等待CPU
- 等待磁盘I/O
- 等待用户输入
- 等待shell命令结果或者终端结果
在当时也没有多少真正的IPC手段, pipe算是一个。不过对于当时的情况来说,一个进程最多只能打开20个fd, 每个用户最多只能开20个进程 也没有多少IPC和复杂I/O的需求。
早期的Unix也没有fd复用的概念, 如果你ssh远程登录Unix系统,系统要同时处理用户的输入,还给用户输出.当时是靠cu
这个命令来实现的,
cu
会创建俩进程,一个负责读一个负责写。因为当时的I/O都是阻塞的,如果要同时读写就得搞俩进程.
Socket
到了1983年,BSD4.2发布的时候,一起发布的还有我们今天耳熟能详的BSD Socket API和TCP/IP
协议栈。
Socket
解决了不一定在同一台机器的不同进程之间的通信问题,是一种有效的IPC手段。Socket
结合TCP/IP
协议还解决了计算机之间的网络通讯问题.
然而读写fd依然是阻塞的, 假如你要处理俩socket,那么可能在阻塞读socket1的时候,socket2的数据因为来不及处理丢失了。
随着Socket
API一起发布的还有大名鼎鼎的select
系统调用, 也即I/O多路复用
的实现。I/O多路复用
通过使用一个系统函数,如select
, 可以同时等待多个fd的可读,可写等状态。
在没有select之前,一般的unix网络程序是这么写的(accept-and-fork模型)
|
|
accept-and-fork
是非常费系统资源的,因为每启动一个新的进程,就需要开辟新的栈,分配虚拟内存等,而且多个进程之间由于缺乏IPC手段,
状态难以共享,对于服务端程序来说是灾难。
Select
Select
发布以后, I/O就能复用了,你可以询问内核哪些fd准备就绪了,然后去发系统调用读数据,
读fd的过程是阻塞的,使用select可以避免无意义的阻塞, 这样即使只有一个进程也可以处理多个socket fd的读写
当时贝尔实验室有个产品叫blit
, 是一种多用户实时终端,和现在的terminal差不多,这就要求应用能同时处理读和写, 像cu
这种靠俩进程
来实现类似多路复用的效果,只能称为一种hack,有了select就能真正多路复用socket fd. 这样你就能在应用进程里方便的处理读和写socket fd而不
无意义的阻塞线程。
随后又发布了Non-blocking
API,但是它和select不同,select是帮助你多路复用, Non-blocking
API是指你在读fd的时候不会阻塞等待数据准备和内核拷贝数据的完成, 这个时候进程可以干别的事情
Non-Blocking
也可以实现类似select的功能,不过那是应用层去做select的功能,也就是应用层需要轮询描述符是都就绪, 假如没有就绪,对fd的read会直接返回一个errno, 代表没有数据可读等。
轮询的缺点是浪费了太多CPU时间,因为read是要发系统调用的,进程会从应用态切换到内核态, 每次这个切换过程都是资源的一种浪费.
select
还会启动多个进程能复用的一个inetd
进程。由于当时一个进程才能开最多20个fd,所以select才会设置
fd_set的最大值是1024,在当时看来是远远用不完的。
- 原文作者:Chris Wang
- 原文链接:https://www.sound2gd.wang/post/java-nio%E5%88%86%E6%9E%902-i/o%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E5%8E%86%E5%8F%B2%E6%9D%82%E8%B0%88/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。