阻塞、非阻塞、同步、异步IO

http://www.cnblogs.com/yunxitalk/p/9031306.html

介绍

在谈及网络IO的时候总避不开阻塞、非阻塞、同步、异步、IO多路复用、select、poll、epoll等这几个词语。在面试的时候也会被经常问到这几个的区别。本文就来讲一下这几个词语的含义、区别以及使用方式。

Unix网络编程一书中作者给出了五种IO模型:

1、BlockingIO - 阻塞IO

2、NoneBlockingIO - 非阻塞IO

3、IO multiplexing - IO多路复用

4、signal driven IO - 信号驱动IO

5、asynchronous IO - 异步IO

这五种IO模型中前四个都是同步的IO,只有最后一个是异步IO。信号驱动IO使用的比较少,重点介绍其他几种IO以及在Java中的应用。

阻塞、非阻塞、同步、异步以及IO多路复用

在进行网络IO的时候会涉及到用户态和内核态,并且在用户态和内核态之间会发生数据交换,从这个角度来说我们可以把IO抽象成两个阶段:1、用户态等待内核态数据准备好,2、将数据从内核态拷贝到用户态。之所以会有同步、异步、阻塞和非阻塞这几种说法就是根据程序在这两个阶段的处理方式不同而产生的。

同步阻塞

当在用户态调用read操作的时候,如果这时候kernel还没有准备好数据,那么用户态会一直阻塞等待,直到有数据返回。当kernel准备好数据之后,用户态继续等待kernel把数据从内核态拷贝到用户态之后才可以使用。这里会发生两种等待:一个是用户态等待kernel有数据可以读,另外一个是当有数据可读时用户态等待kernel把数据拷贝到用户态。
在Java中同步阻塞的实现对应的是传统的文件IO操作以及Socket的accept的过程。在Socket调用accept的时候,程序会一直等待知道有描述符就绪,并且把就绪的数据拷贝到用户态,然后程序中就可以拿到对应的数据。

同步非阻塞

    对比第一张同步阻塞IO的图就会发现,在同步非阻塞模型下第一个阶段是不等待的,无论有没有数据准备好,都是立即返回。第二个阶段仍然是需要等待的,用户态需要等待内核态把数据拷贝过来才能使用。对于同步非阻塞模式的处理,需要每隔一段时间就去询问一下内核数据是不是可以读了,如果内核说可以,那么就开始第二阶段等待。

IO多路复用

IO多路复用也是同步的。

IO多路复用的方式看起来跟同步阻塞是一样的,两个阶段都是阻塞的,但是IO多路复用可以实现以较小的代价同时监听多个IO。通常情况下是通过一个线程来同时监听多个描述符,只要任何一个满足就绪条件,那么内核态就返回。IO多路复用使得传统的每请求每线程的处理方式得到解耦,一个线程可以同时处理多个IO请求,然后交到后面的线程池里处理,这也是netty等框架的处理方式,所谓的reactor模式。IO多路复用的实现依赖于操作系统的select、poll和epoll,后面会详细介绍这几个系统调用。
IO多路复用在Java中的实现方式是在Socket编程中使用非阻塞模式,然后配置感兴趣的事件,通过调用select函数来实现。select函数就是对应的第一个阶段。如果给select配置了超时参数,在指定时间内没有感兴趣事件发生的话,select调用也会返回,这也是为什么要做非阻塞模式下运行。

异步IO

    异步模式下,前面提到的两个阶段都不会等待。使用异步模式,用户态调用read方法的时候,相当于告诉内核数据发送给我之后告诉我一声我先去干别的事情了。在这两个阶段都不会等待,只需要在内核态通知数据准备好之后使用即可。通常情况下使用异步模式都会使用callback,当数据可用之后执行callback函数。

IO多路复用

现在用Java开发的网络服务器通常采用IO多路复用的方式来加快网络IO操作,例如Netty、Tomcat等。IO多路复用的基础是select、poll和epoll。这三个函数是从操作系统的角度上支持的IO多路复用的操作,下面就分别来看一下这三个函数。

select

函数签名如下:

int select(int maxfdp1, fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

maxfdp1为指定的待监听的描述符的个数,因为描述符是从0开始的,所以需要加1

readset为要监听的读描述符

writeset为要监听的写描述符

exceptset为要监听的异常描述符

timeout监听没有准备好的描述符的话,多久可以返回,支持按照秒或者毫秒来配置时间

select操作的逻辑是首先将要监听的读、写以及异常描述符拷贝到内核空间,然后遍历所有的描述符,如果有感兴趣的事件发生,那么就返回。

select在使用的过程中有三个问题:

1、被监控的fds(描述符)集合限制为1024,1024太小了

2、需要将描述符集合从用户空间拷贝到内核空间

3、当有描述符可操作的时候都需要遍历一下整个描述符集合才能知道哪个是可操作的,效率很低。

poll

函数签名如下:

int poll(struct pollfd[] fds, unsigned int nfds, int timeout);

poll操作与select操作类似,仍旧避免不了描述符从用户空间拷贝到内核空间,但是poll不再有1024个描述符的限制。对于事件的触发通知还是使用遍历所有描述符的方式,因此在大量连接的情况下也存在遍历低效的问题。poll函数在传递参数的时候统一的将要监听的描述符和事件封装在了pollfd结构体数组中。

epoll

epoll有三个方法:epoll_create、epoll_ctl和epoll_wait。epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。 通过这三个方法epoll解决了select的三个问题。

1、1024数量限制的问题

通过epoll_create方法来创建一个epoll句柄,这个句柄监听的描述符的数量不再有限制。

2、文件描述符频繁从用户空间拷贝到内核空间的问题

通过观察select的操作会发现描述符从用户空间到内核空间拷贝发生在调用select方法的时候,只要没有注册新的事件或者取消注册事件,每次拷贝的描述符都是一样的。因此epoll引入了epoll_ctl调用,该方法用于注册新事件和取消注册事件。而在epoll_wait的时候并不会拷贝描述符,描述符始终存在于内核空间,当需要修改的时候只要调用epoll_ctl修改一下内核的描述符即可。如此一来便省去了描述符来回拷贝的开销。

3、文件描述符可操作的时候遍历整个描述符集合的问题

在调用epoll_ctl注册感兴趣的事件的时候,实际上会为设置的事件添加一个回调函数,当对应的感兴趣的事件发生的时候,回调函数就会触发,然后将自己加到一个链表中。epoll_wait函数的作用就是去查看这个链表中有没有已经准备就绪的事件,如果有的话就通知应用程序处理,如此操作epoll_wait只需要遍历就绪的事件描述符即可。

epoll在Java中的使用

目前针对Java服务器的非阻塞编程基本都是基于epoll的。在进行非阻塞编程的时候有两个步骤:1、注册感兴趣的事情;2、调用select方法,查找感兴趣的事件。

注册感兴趣的事件

我们在编写Socket的非阻塞代码的时候需要在Selector上注册感兴趣的事情,通常写法是serverSocketChannel.register(selector, SelectionKey.XXX)。来看一下这行代码背后的执行逻辑是什么样的。

注册的时候实际执行的是EPollSelectorImp。该方法主要有以下三步:

1、implRegister方法。在fdToKey的Map中插入channel对应的文件描述法和SelectionKey的映射,当做注册Channel、关闭Channel、取消注册等操作是都是操作此Map。

2、往pollWrapper[Epoll实例]中放入channel实例。

3、往keys[HashSet]中放入SelectionKey

select方法

通过Java的Selector.select方法来获取准备好的键的时候实际执行的代码如下:

首先调用EPollArrayWrapper的poll方法,该方法做两件事:1、调用epollCtl方法向epoll中注册感兴趣的事件;2、调用epollWait方法返回已就绪的文件描述符集合

然后调用updateSelectedKeys方法调用把epoll中就绪的文件描述符加到ready队列中等待上层应用处理, updateSelectedKeys通过fdToKey查找文件描述符对应的SelectionKey,并在SelectionKey对应的channel中添加对应的事件到ready队列。

水平触发LT与边缘触发ET

epoll支持两种触发模式,分别是水平触发和边缘触发。

LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。

ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核会通知你一次,并且除非你做了某些操作导致那个文件描述符不再为就绪状态了,否则不会再次发送通知。

可以看到,本来内核在被DMA中断,捕获到IO设备来数据后,只需要查找这个数据属于哪个文件描述符,进而通知线程里等待的函数即可,但是,LT要求内核在通知阶段还要继续再扫描一次刚才所建立的内核fd和io对应的那个数组,因为应用程序可能没有真正去读上次通知有数据后的那些fd,这种沟通方式效率是很低下的,只是方便编程而已;

JDK并没有实现边缘触发,关于边缘触发和水平触发的差异简单列举如下,边缘触发的性能更高,但编程难度也更高,netty就重新实现了Epoll机制,采用边缘触发方式;另外像nginx等也采用的是边缘触发。

阻塞、非阻塞、同步、异步IO的更多相关文章

  1. 同步异步,阻塞非阻塞 和nginx的IO模型

    同步与异步 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication).所谓同步,就是在发出一个*调用*时,在没有得 ...

  2. 【转载】高性能IO设计 & Java NIO & 同步/异步 阻塞/非阻塞 Reactor/Proactor

    开始准备看Java NIO的,这篇文章:http://xly1981.iteye.com/blog/1735862 里面提到了这篇文章 http://xmuzyq.iteye.com/blog/783 ...

  3. 高性能IO设计模式之阻塞/非阻塞,同步/异步解析

    提到高性能,我想大家都喜欢这个,今天我们就主要来弄明白在高性能的I/O设计中的几个关键概念,做任何事最重要的第一步就是要把概念弄的清晰无误不是么?在这里就是:阻塞,非阻塞,同步,异步. OK, 现在来 ...

  4. 操作系统介绍-操作系统历史,IO,进程的三态,同步异步阻塞非阻塞

    1.操作系统历史 2.进程,IO,同步异步阻塞非阻塞 操作系统历史: 手工操作: 1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式.此时还没有操作系统的概念. 手工操 ...

  5. linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO(转载)

      IO概念 Linux的内核将所有外部设备都可以看做一个文件来操作.那么我们对与外部设备的操作都可以看做对文件进行操作.我们对一个文件的读写,都通过调用内核提供的系统调用:内核给我们返回一个file ...

  6. 理解同步,异步,阻塞,非阻塞,多路复用,事件驱动IO

    以下是IO的一个基本过程 先理解一下用户空间和内核空间,系统为了保护内核数据,会将寻址空间分为用户空间和内核空间,32位机器为例,高1G字节作为内核空间,低3G字节作为用户空间.当用户程序读取数据的时 ...

  7. (转)同步异步,阻塞非阻塞 和nginx的IO模型

    同步异步,阻塞非阻塞 和nginx的IO模型  原文:https://www.cnblogs.com/wxl-dede/p/5134636.html 同步与异步 同步和异步关注的是消息通信机制 (sy ...

  8. Java IO 学习(一)同步/异步/阻塞/非阻塞

    关于IO,同步/异步/阻塞/非阻塞,这几个关键词是经常听到的,譬如: “Java oio是阻塞的,nio是非阻塞的” “NodeJS的IO是异步的” 但是这些东西听多了就容易迷糊,比方说同步是否就是阻 ...

  9. python并发编程之IO模型 同步 异步 阻塞 非阻塞

    IO浅谈 首先 我们在谈及IO模型的时候,就必须要引入一个“操作系统”级别的调度者-系统内核(kernel),而阻塞非阻塞是跟进程/线程严密相关的,而进程/线程又是依赖于操作系统存在的,所以自然不能脱 ...

  10. 理解同步/异步/阻塞/非阻塞IO区别

    5种IO模型 1.阻塞式I/O模型 阻塞I/O(blocking I/O)模型,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区中或者发生错误才返回.进程从调用recvfr ...

随机推荐

  1. JS——缓慢动画封装案例

    手风琴 1.排他思想 2.ul宽度需要大一点,防止li撑开跑下去 3.一个变大其他所有变小,变小不能太小,不然会出现空白 <!DOCTYPE html> <html lang=&qu ...

  2. html——a标签中target属性

    有 4 个保留的目标名称用作特殊的文档重定向操作: _blank 浏览器总在一个新打开.未命名的窗口中载入目标文档. _self 这个目标的值对所有没有指定目标的 <a> 标签是默认目标, ...

  3. IE浏览器new Date()带参返回NaN解决方法

    var start = '2016-01-01 12:12:12'; var date = new Date(start); 得到的时间为NaN: 解决方法: 1.自定义方法 自定义一个NewDate ...

  4. jQuery如何追加tr到table中 添加到头或者尾

    jQuery 添加新内容有以下四个方法: append() - 在被选元素的结尾插入内容 prepend() - 在被选元素的开头插入内容 after() - 在被选元素之后插入内容 before() ...

  5. 兼容ie6的mvvm框架--san

    最近,由于公司项目需要,使用百度mvvm框架san开发了一个兼容ie6的小项目.san的优势是强大的兼容性,能兼容到ie6.当初我自己好奇,尝试用san做了一个小型项目,这里记录一下.如果你也想尝试一 ...

  6. [nodejs]在mac环境下如何将node更新至最新?

    在mac下安装angular-cli时,报出较多错误.初步怀疑是因为node环境版本过低导致. 在mac下,需要执行如下几步将node更新至最新版本,也可以更新到指定版本 1. sudo npm ca ...

  7. POJ-1655 Balancing Act(树的重心)

    Consider a tree T with N (1 <= N <= 20,000) nodes numbered 1...N. Deleting any node from the t ...

  8. 第三节:numpy之数组数学运算

  9. 3.1 一个简单的Java应用程序

        如下一个最简单的应用程序,它将只发送一条消息到控制台窗口中: package myjavapp;   public class FirstSample {       public stati ...

  10. nyoj_289_苹果_20140307

    苹果 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 ctest有n个苹果,要将它放入容量为v的背包.给出第i个苹果的大小和价钱,求出能放入背包的苹果的总价钱最大值. ...