浅谈IO模型
- Bolcking I/O 阻塞IO
- Nonblocking I/O 非阻塞IO
- Multiplexing I/O IO多路复用
- Signal driven I/O 信号驱动IO
- Asynchronous I/O 异步IO
这里的IO主要针对的是网络IO。
首先先来回顾一下一次网络IO都经历了什么
- 网卡(NIC network interface card),首先需要知道有这么一个硬件来帮我们进行物理层和数据链路层的数据转换,以及数据的串并转换。
我们等待的网络数据,首先会放在NIC的ring buffer中(大部分厂家生产的网卡都有这个小玩意的),然后当ring buffer中的数据量达到一定程度之后会触发硬件中断,硬件中断实际上就是 一根物理走线连到cpu的一个引脚,直接打断cpu的执行,然后cpu会做两件事情,处理该中断,然后把中断处理流程唤醒。由于cpu的资源很宝贵,硬件中断处理程序肯定不能太大,占用过多的cpu时间。所以一般都是 让cpu知道 有中断发生了,后面要安排时间来处理这个中断,不然网卡里的数据塞不下了。
再然后操作系统会接管接下来的事情,先将ring buffer中的数据拷贝到kernel buffer中,然后才能将kernel buffer中的数据通过某个系统调用例如read将其拷贝到用户空间的buffer中。
这个完整的过程 推荐看 飞哥的文章,搜索关键字 张彦飞就能找到,他写的epoll,和网络收发包全流程,文字,图片都有,很清晰。需要耐心查看并理解。
Blocking IO
阻塞IO,也是最常见的IO形式之一,大部分系统调用,例如read,write,recv,send都是阻塞函数,当没有数据到达时read就会阻塞。或者没有数据写入时,write就会阻塞。可以用下面的图来进行解释
NonBlocking IO
非阻塞IO,将文件描述符或者socket设置为NONBLOCK的状态。
IO multiplexing
多路IO复用,将轮询文件描述符或socket这样的操作托管给操作系统,由OS来为我们实现这样的动作,我们只需要调用相应的系统调用API即可。
Signal Driven IO
信号驱动型IO
Asynchronous IO
异步IO,将所有的事情全部托管给操作系统。Linux并没有实现真正的异步IO,Windows实现了,应该是叫IOCP吧。 真正意义上来 异步IO,只能是操作系统层面支持。
所以其实上面谈到了两个方面,希望不要搞混了。
阻塞 与 非阻塞
异步 与 同步
这是两个角度,两个不同方面的描述。
这里只从IO的层面考量,网络IO 和 磁盘IO 都统称为IO。
阻塞 与 非阻塞 描述的是 所调用的IO函数,会不会在调用之后"卡住",如果调用后 立刻返回,说明不阻塞,如果调用后,一段时间才返回 甚至 一直都不返回,说明是阻塞式。
而 异步与 同步,说的是 处理过程。 视角更高,层次更抽象。
例如,我是一个 服务器,运行着一个 登录鉴权服务。 当有用户登录时,我需要做几件事情:
- 取出登录协议中用户的唯一标识uid,请求数据库 拿到该用户的 鉴权信息,密码,二级密码之类的。
- 数据库返回后,对比登录协议中所包含的密码 和 从数据库中查询到的密码,进行鉴权。
- 鉴权成功,则从用户的另一个 存放基本数据 和 游戏数据的数据库中取出数据,返回给该用户。
这个过程中,有好几次请求数据库的操作,这是明显的网路IO操作。 如果我们在第1步中,请求用户鉴权信息的时候,可以做到这样的效果:数据库还没有返回查询结果,应用程序 不会在这死等(也就是阻塞),而是接着处理其他请求。 如果可以做到,那这个过程 就称之为 异步操作。
实现异步操作的方式有很多:最直观的有多线程、协程。也就是主线程不阻塞,让其他线程去阻塞。
基于事件通知,也就是 我要请求数据库,那就把这个请求 打包成一个事件,然后发出这个事件之后,发出请求的这一方就可以不用管了,只用等 事件通知,等什么事件的通知了,就等 数据库回包的事件通知。
那就有同学要说了,我的业务逻辑 和 数据库的回包强相关,也就是 我必须等 第一次请求返回之后,才能继续第二次请求。那这种情况 就不适合用异步做了,业务逻辑上 处理顺序有先后依赖关系,这个只能同步了。因为你的后一步要执行,必须得等前一步执行完。
那什么场景下 适合用 异步的思想呢?
首先业务逻辑 没有 顺序依赖,可以打乱执行步骤。
其次,要追求处理效率,提升服务器性能,异步是不可或缺的武器。
但基于Linux的程序,实际上没有真正的异步IO(什么是真正的异步IO呢?个人理解:你只需要调用一下IO类的系统调用,接口立马返回,等到IO过程结束,操作系统自动通知 应用程序 IO过程已经结束,数据已经拷贝到用户空间了,你的应用程序可以处理了。)