通俗理解五种 IO 模型

mark

任何 IO 过程中,都包含两个步骤:第一是等待,第二是拷贝。而且在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间。让 IO 更高效,最核心的办法就是让等待的时间尽量少。

理解五种 IO 模型

先来看看五种 IO 模型:

1、阻塞式 IO:在内核将数据准备好之前,系统调用会一直等待。所有的套接字,默认都是阻塞方式,阻塞式是最常见的 IO 模型, 阻塞式 IO 属于同步 IO 模型

例子:A 拿着一支鱼竿在河边钓鱼,并且一直在鱼竿前等,在等的时候不做其他的事情,十分专心。只有鱼上钩的时,才结束掉等的动作,把鱼钓上来。

mark

2、非阻塞式 IO:如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回 EWOULDBLOCK 错误码,非阻塞 IO 往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询。这对 CPU 来说是较大的浪费,一般只有特定场景下才使用, 非阻塞式 IO 也属于同步 IO 模型

例子: B 也在河边钓鱼,但是 B 不想将自己的所有时间都花费在钓鱼上,在等鱼上钩这个时间段中,B 在做其他的事情(刷牛客或者刷知乎),但 B 在做这些事情的时候,每隔一个固定的时间检查鱼是否上钩。一旦检查到有鱼上钩,就停下手中的事情,把鱼钓上来。

mark

3、信号驱动 IO:内核将数据准备好的时候,使用 SIGIO 信号通知应用程序进行 IO 操作, 信号驱动 IO 也属于同步 IO,信号驱动是不是有点异步的感觉? 但是在将数据从内核复制到用户空间这段时间内用户态进程是阻塞的,所以也属于同步 IO

例子: C 也在河边钓鱼,但与 A、B 不同的是,C 比较聪明,他给鱼竿上挂一个铃铛,当有鱼上钩的时候,这个铃铛就会被碰响,C 就会将鱼钓上来。

mark

4、IO 多路复用:虽然从流程图上看起来和阻塞 IO 类似,实际上最核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态,IO 多路复用也属于同步 IO

例子: D 同样也在河边钓鱼,但是 D 生活水平比较好,D 拿了很多的鱼竿,一次性有很多鱼竿在等,D 不断的查看每个鱼竿是否有鱼上钩。增加了效率,减少了等待的时间。

mark

5、异步 IO:由内核在数据拷贝完成时,通知应用程序 (而信号驱动是告诉应用程序何时可以开始拷贝数据), 异步 IO 就是异步 IO,哈哈

例子:E 也想钓鱼,但 E 有事情,于是他雇来了 F,让 F 帮他等待鱼上钩,一旦有鱼上钩,F 就打电话给 E,E 就会将鱼钓上去。

mark

阻塞程度:阻塞 IO > 非阻塞 IO > 多路转接 IO > 信号驱动 IO > 异步 IO,效率是由低到高

理解同步与异步通信

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了;换句话说,就是由调用者主动等待这个调用的结果;

异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用;

对于同步通信来说,都是由调用者主动等待这个调用的结果 ,无论是 “干等”,还是轮询式的等,还是信号通知式的等,还是一次等多个的形式,都是需要主动去等的, 对于异步通信来说,这个调用就直接返回了,所以没有返回结果 ,不需要调用者主动的等待。

理解阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起 ,比如阻塞式 IO 就是等待调用结果的时候会发生阻塞,啥也干不了,调用线程只有在得到结果之后才会返回;

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程 ,比如非阻塞 IO,进程在等待的同时,还可以做其他事情。包括信号驱动也属于非阻塞,但是在数据从内核拷贝到用户空间时也是阻塞的;