也谈阻塞、非阻塞、同步、异步

最近在招聘中,聊到基础的网络编程的时候,发现不少人对BIO、NIO、AIO等理解非常模糊,觉得有必要写文章来纠正下很多人的误解。 
在谈这些之前,非常有必要先介绍下Unix 5种IO模型: 
阻塞: 
阻塞是最常用的IO模型,默认情况下所有的文件操作都是阻塞的。以套接字编程为例。在进程空间中调用recvfrom,其系统调用直到数据报文到达且被拷贝到应用程序进程的缓存区(或者发生错误)后才返回,期间一直在等待。进程在从调用recvfrom开始到它返回的整段时间内是被阻塞的。有一张很经典的图: 
 
非阻塞: 
调用recvfrom从应用层到内核的过程中,如果该缓冲区没有数据的话,则直接返回一个EWOULDBLOCK的错误,一般会轮询的进行检查状态,看内核空间有没有数据来。直到有数据,最后完成拷贝。如下图: 
 
IO多路复用: 
Linux系统提供的select/poll/epoll,进程将一个或者多个FD(文件描述符)传递给一个或者多个poll/select系统调用,阻塞在select。select和poll可以帮助侦听很多的FD是否准备就绪。但是,select和poll是顺序扫描去检查FD的就绪状态,效率比较低,而且支持的FD数量有限(没记错的话,默认好像是1024还是2048,具体记不清)。而epoll是通过事件驱动的方式,当有FD准备就绪的时候,立即回调函数rollback。如图: 
 
谈到epoll,不得不提一个经典的问题,apache和nginx的对比,为什么nginx比apache效率高很多,这就是根本的原因。 
信号驱动: 
这种模型在实际应用的非常少,这里不做过多介绍,可以看图: 
 
异步: 
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核空间拷贝到自己的缓冲区)通知。异步IO的主要特点是完成操作后主动通知。如图: 

好,上面的可能有点抽象。下面用通俗点的语言来总结一下阻塞,非阻塞,同步,异步。
阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待; 
同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞; 
异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。 
再举个网上流传的,非常容易理解的例子: 
老张爱喝茶,废话不说,煮开水。 
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。 
1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻 
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。 
3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大 
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)老张觉得自己聪明了。 
所谓同步异步,只是对于水壶而言。普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。 
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。所谓阻塞非阻塞,仅仅对于老张而言。立等的老张, 
阻塞;看电视的老张,非阻塞。情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。 
虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

 

posted @ 2016-01-05 14:19  丁码农  阅读(1628)  评论(1编辑  收藏  举报