武侠小说里有很多的“心法”和“招式”。计算机技术里的“心法”和“招式”呢,我们可以简称为“道”和“术”;
“道” 最基础的计算机理论,隐藏于表象之下,非常抽象、晦涩难懂,需要用具象化的事物加以理解;
“术” 具体的技艺,它有可能是一门语言,比如:python 出手见效快;
我们今天要给大家讲的底层的IO就属于“道”的范畴,看上去简单,实则抽象。并且在它之上衍生出了语言层面用于实战的技术,比如我们熟悉的java语言中的NIO或者像Netty这样的框架。
一、混乱的 IO 概念
IO是Input和Output的缩写,即输入和输出。广义上的围绕计算机的输入输出有很多:鼠标、键盘、扫描仪等等。而我们今天要探讨的是在计算机里面,主要是作用在内存、网卡、硬盘等硬件设备上的输入输出操作。
二、用户空间和内核空间
硬 件 层(Hardware)
包括和我们熟知的和IO相关的CPU、内存、磁盘和网卡几个硬件;
内核空间(Kernel Space)
计算机开机后首先会运行内核程序,内核程序占用的一块私有的空间就是内核空间,并且可支持访问CPU所有的指令集(ring0 - ring3)以及所有的内存空间、IO及硬件设备;
用户空间(User Space)
每个普通的用户进程都有一个单独的用户空间,用户空间只能访问受限的资源(CPU的“保护模式”)也就是说用户空间是无法直接操作像内存、网卡和磁盘等硬件的;
三、IO模型
1、 BIO(BlockingIO)
我们先看一下大家都熟悉的BIO模型的 Java 伪代码:
ServerSocket serverSocket = newServerSocket(8080); // step1: 创建一个ServerSocket,并监听8080端口
while(true) { // step2: 主线程进入死循环
Socketsocket = serverSocket.accept(); // step3: 线程阻塞,开启监听
BufferedReader reader = new BufferedReader(nweInputStreamReader(socket.getInputStream()));
System.out.println("read data: " + reader.readLine()); //step4: 数据读取
PrintWriter print = new PrintWriter(socket.getOutputStream(), true);
print.println("write data"); // step5: socket数据写入
}
问题
以上三个步骤:accept(...)、read(...)、write(...)都会造成线程阻塞。上述这个代码使用了单线程,会导致主线程会直接夯死在阻塞的地方。
优化
我们要知道一点“进程的阻塞是不会消耗CPU资源的”,所以在多核的环境下,我们可以创建多线程,把接收到的请求抛给多线程去处理,这样就有效地利用了计算机的多核资源。甚至为了避免创建大量的线程处理请求,我们还可以进一步做优化,创建一个线程池,对暂时处理不了的请求做一个缓冲。
四、同步、异步
这边顺便提两种大家可能会经常听到的模式:Reactor和Preactor。
Reactor 模式:主动模式。
Preactor 模式:被动模式。
五、总结
本篇文章从底层讲解了下从BIO到NIO的一个过程,着重介绍了IO多路复用的几个系统调用select()、poll()、epoll(),分析了下各自的优劣,技术都是持续发展演进的,目前也有很多的痛点。后续会继续给大家介绍下与此相关的“零拷贝”技术,以及Java NIO和Netty框架。
高品质全面课程升级,2020最新SVIP豪华版