一、案例:
故事:老王烧开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
老王想了想,有好几种等待方式
1.老王用水壶煮水,并且站在那里,不管水开没开,每隔一定时间看看水开了没。-同步阻塞
老王想了想,这种方法不够聪明。
2.老王还是用水壶煮水,不再傻傻的站在那里看水开,跑去寝室上网,但是还是会每隔一段时间过来看看水开了没有,水没有开就走人。-同步非阻塞
老王想了想,现在的方法聪明了些,但是还是不够好。
3.老王这次使用高大上的响水壶来煮水,站在那里,但是不会再每隔一段时间去看水开,而是等水开了,水壶会自动的通知他。-异步阻塞
老王想了想,不会呀,既然水壶可以通知我,那我为什么还要傻傻的站在那里等呢,嗯,得换个方法。
4.老王还是使用响水壶煮水,跑到客厅上网去,等着响水壶自己把水煮熟了以后通知他。-异步非阻塞
老王豁然,这下感觉轻松了很多
同步和异步是相对于操作结果来说,会不会等待结果返回;
阻塞和非阻塞是相对于线程是否被阻塞;
二、分析
OS里面有内核态和用户态两种,程序进行IO操作的时候一般是两步
第一步是IO初始化也就是准备好IO操作 —— 其中第一步决定同步还是异步
第二步就是真正的IO操作 —— 第二步决定堵塞还是非堵塞的
关于堵塞和非堵塞:
为什么这么说呢, 我们以Linux为例来看看这个关键字的组合
四种案例:
-
同步堵塞IO
就是最最普通的write/read操作,以读操作为例, 程序调用内核接口,然后等待系统返回,内核做读写初始化操作(寻址,读取原始数据到内核等),接着将数据读出到内核态,然后把数据从内核态拷贝到用户态下并返回结果给程序,然后程序才知道完成整个读操作
在内核初始化到将数据从存储介质取出到内核态内存中的这段时间程序就是一直等待,期间CPU的状态是wait的,夸张一点,如果内核调用一次读的操作是1秒的话,那么这一秒内程序是将CPU占着不做任何事情的,是不是感觉好浪费 -
同步非堵塞IO
这里再内核调用里面会有一个设置参数叫O_NONBLOCK,使用这个的时候就会变成了非堵塞的调用;
其实从这个参数差不多可以猜测出堵塞和非堵塞是针对内核调用来说的,因为这个参数直接作用的就是内核调用;
以读操作为例,程序首先向内核发出一个读操作的调用,这个时候系统会在很短的时候返回一个状态,表示读操作是否初始化完成,大多数时候是返回不能读初始化(因为有资源竞争),接着程序会继续像死循环一个的发起这个请求直到内核返回读操作已经准备好(记住死循环的时候程序实际上被读这个操作的时候实际不能做别的事情,所以是同步的;但可以执行其它事情;),这个时候程序在才能将数据从内核态拷贝到用户态后并且返回,这个读过程就算完成了;
虽然内核态是是非堵塞的,但是程序这个线程里面在完成读操作之前也没有能做别的事情, 所以这里还是同步的. 相对同步堵塞IO来说,这种方式可以让程序不用占着CPU时间也不给别人用的情况;
由于操作是循环调用查看状态的,所以再内核准备好数据到程序下一次查看状态之间存在延时,这样会导致系统的吞吐量下降 -
异步堵塞IO
采用Select这种阻塞的方式来调用非阻塞的内核调用;
虽然读和写的操作并没有被堵塞住,而是由Select中获取了I/O操作符再进行下一步操作,这里的数据真正的操作还是被堵塞住了,但是这个的好处就是可以同时再一个线程里面对多个IO操作进行处理,这种顺序处理的效率确实不高,再有的程序里面会启动多个线程来做Select的操作提高性能.这个一个的好处就是再同一个线程内可以有多个IO操作同时进行,他不能提高单个IO的吞吐量,但是可以提高程序的IO并发能力,从而提高整体的IO吞吐量. -
异步非堵塞IO
这个厉害了,这就是我们提得比较热的一个概念,叫AIO,他采用的是回调的方式实现异步操作,然后也使用非堵塞的参数,这样一来程序不需要死循环来监听IO操作符,内核准备好了就会通知程序做对应的事情,而程序再读写等待的时候就可以把剩下的CPU时间用在做别的业务处理.这个的好处就太多了,首先他吧CPU时间让出来了,可以做别的事情,其次是回调方式的,只要数据准备好就可以回调让程序把数据从内核拷贝走.坏的地方么我只能说回调对于程序员的编码要求确实有点高的