Linux环境开发(一):同异步、阻塞的IO模型相关问题

  其实这几个概念可能在实际的开发中并没有什么需要注意的,因为你调用什么函数就知道接下来需要怎么做了,但是如果要确实分清这几个概念的区别和联系,还是需要动动脑筋的。下面这幅图十分的好:
LinuxIO模型矩阵

  总结来说,区分的关键点在于:
  (1) 同步-异步:IO资源可用与否是自己去检测,还是依赖于状态、信号、回调等其它机制来通知;
  (2) 阻塞-非阻塞:IO调用的函数在资源不可用时候是否立即返回,还是被挂起状态直到资源可用;

一、同步阻塞IO

  算是最简单最容易理解的模型了,且Linux默认的IO模型就是这样的,比如你用open打开文件或者socket创建套接字而没有显式使用O_NONBLOCK/SOCK_NONBLOCK参数的时候,那么后续使用read/write/recv/send等函数的时候,每当资源不可用,应用程序将阻塞在这些调用上面,直到这些调用成功后返回,程序才会继续执行下去。
  这时候的阻塞,内核会将程序进程切换到睡眠的状态,当内核完成IO将数据返回到用户态可用的时候,程序会被切换继续运行下去。

二、同步非阻塞IO

  通常在open和socket调用时候添加了O_NONBLOCK/SOCK_NONBLOCK标志,或者采用ioctl等机制后续设置了这个标志的时候,当调用上面的IO操作函数时候,如果此时资源不可用,调用会立即返回(通常返回EAGAIN/EWOULDBLOCK错误)。
  这个时候应用程序通常的做法是sleep一会儿,或者干点别的事情,然后再次进行IO调用看资源是否可用了。总体来说该方法是比较低效率的,如果忙等待会浪费很多计算资源;且如果休眠时间长或者干别的事情长,那么总体IO响应将会变得很不及时。虽然习惯上称为“异步模式”开发,但本质上还是同步非阻塞的类型,而正规上称为基于事件驱动的开发方式。

三、异步阻塞IO

  这其实是一个带阻塞通知的非阻塞IO,是select/poll/epoll函数族的典型情况。从使用上来说,虽然将fd/socket设置为了非阻塞形式,但是select/poll/epoll的调用却是阻塞的,所以这里实际上将阻塞从原先的IO操作转嫁挪动到的资源的侦听操作(epoll_wait)上面了。

四、异步非阻塞IO

  最复杂的情况了,在linux中有aio_xxxx()对应的函数族,其最大的特点是程序的执行和IO操作可以重叠执行:程序调用aio_xxxx()后立即返回,然后程序就执行其它的代码了,而内核完成IO操作之后,会通过状态、信号、回调函数等机制完成IO操作之后对应要处理的内容。
  Windows的完成端口就是典型的异步非阻塞IO模型。而在Linux环境下,感觉异步非阻塞IO不太遭怎么待见,网站基本的构架都是基于epoll这类异步阻塞IO设计的。我想,这可能是因为异步非阻塞IO的优势不是特别的明显,而异步阻塞IO比较符合一般人的思维编程习惯吧。

  注:后两者的差异,其实就是典型高性能IO设置中常说的Reactor模式和Proactor模式:Reactor中回调通知的是关心的事件或者资源是否就绪了,而真正需要应用程序自己读取或者写入数据;而Proactor模式中,应用程序不需要进行实际的读写过程,操作系统会读取缓存区或者写入缓存区到真正的IO设备,当收到通知的时候,真正的读写事件已经完成了。相比较而言,Proactor需要先分配内存,然后再处理IO操作,可以实现数据的Zero-Copy;同时Reactor模式所有的工作都在回调函数中处理,当回调函数任务繁重的时候,容易导致回调队列拥塞。
  然后据陈硕所言,Linux下面还是Reactor模式较为成熟,Linux下的aio基本没有开发和完善的动力,这些异步操作接口很少使用在网络编程上,而是boost::asio为了跨平台,选取了Proactor模式而已(不过boost.asio也支持Reactor模式操作的);此外,常用的Libevent就是典型的Reactor模式,而Libevent在Windows下只支持select的异步模式,不过如果是在Windows下的网络开发,可能大部分人还是会直接使用操作系统提供的完成端口机制吧,因为几乎没有看到程序先在Windows平台上开发,然后再兼容到Linux平台的。

本文完!

参考