有人觉得堵塞就是同步,非堵塞就是异步,其实以前我也是这么想的,其实同步与堵塞这完全是两码事,所以写篇文章来说说为什么是两码事,也顺便说说各种组合的可以达到的效果,帮助大家了解底层的原理.
首先需要了解这些概念,OS里面有内核态和用户态两种,程序进行IO操作的时候一般是两步,第一步是IO初始化也就是准备好IO操作,第二步就是真正的IO操作.其中第一步决定同步还是异步,第二步决定堵塞还是非堵塞的,为什么这么说呢,我们以Linux为例来看看这个关键字的组合
1. 同步堵塞IO
2. 同步非堵塞IO
3. 异步堵塞IO
4. 异步非堵塞IO
同步堵塞IO:就是最最普通的write/read操作,以读操作为例, 程序调用内核接口,然后等待系统返回,内核做读写初始化操作(寻址,读取原始数据到内核等),接着将数据读出到内核态,然后把数据从内核态拷贝到用户态下并返回结果给程序,然后程序才知道完成整个读操作,在内核初始化到将数据从存储介质取出到内核态内存中的这段时间程序就是一直等待,期间CPU的状态是wait的,夸张一点,如果内核调用一次读的操作是1秒的话,那么这一秒内程序是将CPU占着不做任何事情的,是不是感觉好浪费.
同步非堵塞IO:这里再内核调用里面会有一个设置参数叫O_NONBLOCK,使用这个的时候就会变成了非堵塞的调用,(其实从这个参数差不多可以猜测出堵塞和非堵塞是针对内核调用来说的,因为这个参数直接作用的就是内核调用),这里会有一个状态通知的概念,程序不会立即完成IO操作,还是以读操作为例,程序首先向内核发出一个读操作的调用,这个时候系统会在很短的时候返回一个状态,表示读操作是否初始化完成,大多数时候是返回不能读初始化(因为有资源竞争),接着程序会继续像死循环一个的发起这个请求直到内核返回读操作已经准备好(记住死循环的时候程序实际上被读这个操作的时候实际不能做别的事情,所以是同步的),这个时候程序在才能将数据从内核态拷贝到用户态后并且返回,这个读过程就算完成了.虽然内核态是是非堵塞的,但是程序这个线程里面在完成读操作之前也没有能做别的事情,所以这里还是同步的.相对同步堵塞IO来说,这种方式可以让程序不用占着CPU时间也不给别人用的情况.,另外由于操作是循环调用查看状态的,所以再内核准备好数据到程序下一次查看状态之间存在延时,这样会导致系统的吞吐量下降.
异步堵塞IO:这里就是我们java里面用到的Select或者Clib里面的poll来实现的,这里面实际也用到上面提到的非阻塞的参数,只是采用Select这种阻塞的方式来调用非阻塞的内核调用.虽然读和写的操作并没有被堵塞住,而是由Select中获取了I/O从操作符再进行下一步操作,这里的数据真正的操作还是被堵塞住了,但是这个的好处就是可以同时再一个线程里面对多个IO操作进行处理,这种顺序处理的效率确实不高,再有的程序里面会启动多个线程来做Select的操作提高性能.这个一个的好处就是再同一个线程内可以有多个IO操作同时进行,他不能提高单个IO的吞吐量,但是可以提高程序的IO并发能力,从而提高整体的IO吞吐量.
异步非堵塞IO:这个厉害了,这就是我们提得比较热的一个概念,叫AIO,他采用的是回调的方式实现异步操作,然后也使用非堵塞的参数,这样一来程序不需要死循环来监听IO操作符,内核准备好了就会通知程序做对应的事情,而程序再读写等待的时候就可以把剩下的CPU时间用在做别的业务处理.这个的好处就太多了,首先他吧CPU时间让出来了,可以做别的事情,其次是回调方式的,只要数据准备好就可以回调让程序把数据从内核拷贝走.坏的地方么我只能说回调对于程序员的编码要求确实有点高的.
所以总结一下这四个方式:同步堵塞IO在IO操作开始的时候就堵塞程序,这个时候还不能重复的进行别的IO操作,同步非堵塞IO解决了内核可以同时处理来自程序的多个IO请求.而异步堵塞主要是解决在一个线程里面处理多个IO的操作,他并没有对IO进行堵塞,但是对事件进行了堵塞(Select实际上实在选择事件),最后的终极大咖异步非堵塞IO,实现了内核非堵塞和回调通知的方法,让IO的操作更高效.
那是不是异步非堵塞是最好的呢,这个不会一定,我们在做大数据的时候会知道程序有CPU密集型和IO密集型两种区别,至于那种类型使用哪种方式的IO模式这个就需要看攻城狮自己的判断了
补充:
忘了说了,LInux在2.6内核里面才讲AIO作为标准标准特性.