阻塞、非阻塞、同步、异步与I/O编程

在涉及到网络编程方面时,似乎总会接触到这些概念:阻塞式I/O,非阻塞式I/O,I/O复用,信号驱动式I/O,异步式I/O;他们之间的差异往往容易被混淆,因此也很有必要从本质上认清这些概念.

阻塞与非阻塞

阻塞与非阻塞是相对的,通俗而言二者的区分主要在于“在接收到有效数据前是否返回”。

通常而言,一个输入操作主要包含两步:(1)等待数据就绪(2)从内核空间拷贝数据到用户空间,如果采用阻塞的方式,也就意味着,进程必须等待条件(1)满足后才会返回,否则会阻塞直到收到了有效数据。而如果采用非阻塞的方式,也就意味着,即使缓冲区没有有效数据,也会返回一个值(比如-1)。

最直接的例子也就是read()函数,如果采取阻塞式read,在缓冲区内为空时,进程将会一直等待,直到接收数据后,read()将会返回接收到的数据大小。而采用非阻塞式read()的情况下,其会立即返回值,如有数据就返回接收到的数据大小,否则返回-1;

 

同步与异步

同步与异步是相对的,通常来说,二者的主要区分在于"是否需要等待当前操作进行完才能进行下一步操作"

最简单的例子就是“小明需要吃完饭再看电视”还是“小明边吃饭边看电视”。

如果采用同步方式进行I/O操作,则需要等待当前I/O操作结束进程才能进入下一阶段。你可能回想这与阻塞有什么区别呢?实际上,阻塞只是针对“数据”而言,无论阻塞还是非阻塞,其在正确执行完I/O操作前并不能执行其他操作,即使是非阻塞下能够立刻返回值,其也不代表当前的I/O操作已经终止。所以这两者都是同步I/O。

而如果采取异步的方式,意味着我们不需要等待当前I/O操作结束,就能够执行其他操作,当先前的I/O操作结束时,其会将返回值回调到用户空间。实际上异步方式在网络编程中并不是很常见。多见于文件中的I/O操作

 

下图展示了几种I/O模型

I/O多路复用

I/O多路复用是网络编程中最常见的形式,其流程与阻塞式I/O很像,但其能够同时处理多路I/O。

目前为止,UNIX网络编程中常见的I/O多路复用方法包括三类:select()、poll()、epoll()

select()

select是三者中最早出现的方法,其提供了名为FD_SET的数据结构,用来存放需要监听的文件描述符,本质就是一个数组。每次调用select时需要重新把用户态的文件描述符信息拷贝到内核态。

当有某一文件描述符被“激活”(比如有数据到来,或者缓冲区清空了),FD_SET的内容将借由内核状态的改变而修改。select返回的是“被激活”的句柄数目。当返回值大于0,即会采用“轮询”的方式来检测哪些描述符被激活,并随之调用相应的回调函数。

尽管select机制让用户能够同时监听多路I/O,但随着FD_SET集合的变大,每次select进行拷贝的开销也会加大,同时因为回调前采取轮询的方式,其时间开销会随着FD_SET的大小而线性增加,另外,为了减少数据拷贝带来的破坏,FD_SET的大小被加以限制为1024不可更改,这些都是select方式所存在的缺陷。

 

Poll()

Poll()方法是在select()后出现的,其依旧采取轮询的方式来进行回调,但相比select(),Poll()可存储的文件描述符数目没有了大小限制

 

epoll()

相比select()和poll(),epoll()方式的主要改进之处在于

(1)只有在epoll_ctl操作时会将用户态信息拷贝到内核态,而在创建和修改时不会有额外拷贝开销

(2)其不再基于轮询方式来检测哪些描述符被激活,而是用一个ReadList结构来存储所有被激活的描述符。因此用户回调时只需要遍历ReadList即可,尤其当描述符数目特别大时,epoll的效率将会显著高于前两者。(ReadList依赖于底层红黑树结构的实现),是一种基于事件的触发方式

(3)epoll支持水平触发和边沿触发两种方式,二者的区别是:

水平触发时,如果当前某个描述符中尚有未处理的数据,如果本轮次不进行处理,到了下一轮次其依然处于激活状态,可继续处理

边沿触发,意味着只有当“状态改变”时才会将某个描述符激活,意味着如果本轮次位对某一激活的描述符进行处理,之后便不会再处理。

最简单的例子:

如果原本缓冲区为空,而在某一时刻缓冲区内被填入数据,那么无论是水平触发还是边沿触发都会将描述符激活

但如果本轮次没有来得及处理该缓冲区,到了下一轮次,在水平触发模式下,发现缓冲区内仍有数据,所以描述符被激活,可继续处理;而如果是边沿模式,缓冲区的状态并没有发生改变(状态指的是空、非空),所以在缓冲区被情空前,该描述符将不再可读,也不会被处理。

 

下表显示了三者的区别

 

 selectpollepoll
回调方式轮询(遍历)轮询(遍历)直接根据ReadList回调
底层实现数组链表红黑树
回调时间复杂度O(n)O(n)O(1)
最大维护数目1024(x86)/2048(x64)无上限无上限
描述符信息拷贝方式调用select时从用户态拷贝到内核态调用poll时从用户态拷贝到内核态当调用epoll_ctl时拷贝进内核并保存,后即使调用epoll_wait也不拷贝

目前为止,epoll是unix网络编程开发最广泛使用的方法之一。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值