阻塞非阻塞-同步异步


硬件上下文切换

每个进程都拥有自己的内存空间,CPU任何时间只能运行一个进程. 运行之前,每个进程需要将内存状态复制到CPU的寄存器才能工作。这种复制就是进程的上下文切换

 

二种切换都会导致成本升高
1.进程内存-CPU寄存器相互切换 硬件上下文 解决:上下文切换一般都是由阻塞引起的,所以使用非阻塞
2.进程二种运行状态的切换 用户态-内核态  解决:减少不避要的系统调用。

 

阻塞的特点:进程睡眠

阻塞: 如果条件未就绪,'你'必须死等它就绪;进程睡眠(睡眠的缺点就是会让出CPU控制权)
非阻塞:如果条件未就绪,'你'可以转身作别的事情;进程可以作任何想做的事情,不过通常是低效的轮询。(轮询的特点是CPU寄存器一直被当前进程使用。轮询不是一种好的方式,可以使用wait/notify机制)

以这种理解方式,阻塞/非阻塞只对同步操作有意义;异步I/O总是意味着进程不会因为I/O陷入睡眠。

 

阻塞非阻塞示例:

比如一个人去银行办业务,拿到流水号之后,一边抬头看显示屏幕上当前显示的流水号一边打电话这就是同步非阻塞。(当前进程在等待的过程中可以做其它事情,注意,如果将柜台触发流水号理解为消息通知的话,就能避免用户不停的在二件事情上来回切换,此时用户只需要打你的电话就OK了,不用再抬头看流水号是否轮到了自己,这件事情交给了银行柜台去做,因为柜台与用户是二个主体各做一件事情,这不会象用户一个人同时做二件事情而引起低效轮询)


一个广义概念的read/write应该包含的时间:

数据就绪的时间+数据读写的时间
 

 

注:进程等待数据就绪,可以阻塞(傻等),也可以非阻塞(同时做其它事情)。进程进行数据读写可以使用阻塞或非阻塞的方法。

 

select是同步还是异步:

经常看到一些技术博客或书籍讲IO的多路就绪通知中的select方式是异步阻塞。

1.如果按照同步与异步只取决于消息通知,不包括处理消息的话,select是异步的方式。示例:只要柜台通知了我就算异步,至于办理什么业务即如何处理消息不参与到同步异步的划分。此时select是异步阻塞方式。

2.如果按照UNP的划分的话,同步/异步不仅仅包括消息通知,还包括了对消息的处理。select并没有什么神秘的地方,它只是监控了多个fd的状态而不是一个。select实际上完成的只是read/write的第一部分:等待条件就绪;唯一的改进是可以等待多个条件。"select + read/write"的调用形式容易产生"系统通知我条件就绪"的假象,可实际上你不过是在条件就绪的时候醒来,然后仍然亲自动手完成了数据复制的操作。 select通知进程之后,进程还要切到内核态调用一些系统函数来读写数据,比如NIO中的Channel在获得相应的Channel注册的事件已经触发之后,还要负责读写数据。所以,如果基于UNP的划分,NIO是属于同步非阻塞的方式,read/write、read + NON_BLOCK、select、signal driven I/O 都属于同步I/O; 它们的共同特点是:将数据从内核空间复制到到用户空间的这个操作,是由用户空间的代码显式发起的。

 

select示例:

柜台R:只能取款
柜台W:只能存款

read: 亲自在柜台R排队(进程睡眠) + 取款
write: 亲自在柜台W排队(进程睡眠) + 存款

select + read/write : 亲自同时在R、W两个柜台排队(进程睡眠) + (存款|取款|存款+取款)

 

注:我觉得二种见解都正确,角度不同而已。

 

什么是异步:

AIO : 告诉心腹小弟要取款若干,然后忙别的事情;小弟取款完毕将其如数奉上。只有AIO属于异步I/O;内核不露声色的将数据从内核空间复制到用户空间,然后通知进程。(NIO在就绪之后,进程还要切换到内核态调用系统函数将数据读写到用户态的内存空间。AIO直接告诉进程你可以操作用户态空间里的数据了。)

 

异步示例:交代要做的事情,然后忙其他的事情;'别人'(OS)会充当你的跑腿,在全部做完你交待的事情,然后通知你(callback);比如你交待取钱,可以做其它事情,他最终取完钱会把钱交到你的手上。

 

 

参考博客:http://www.cppblog.com/converse/archive/2009/05/13/82879.html

-------------------------------------------------------------------------------

之前的想法:

读写方法可以分为阻塞与非阻塞二类。NIO中Channel的读写方法是非阻塞的,Channel 是一个全新的原始 I/O 抽象。Channel用来表示以前的InputStream,OutputStream表示的数据的源与目的。Channel通过Buffer中转数据。而传统的IO类的读写操作默认是阻塞方式的。 


阻塞的读方法中,读阻塞与读方法有关,读方法的参数决定了要读多少数据,如果没有读满, 就会一直阻塞. 直到读满返回。
写阻塞类似,具体也取决于写方法的参数。

对于阻塞而引起的等待的问题,解决方法有二种,一种是通过多执行流来处理,比如多线程,多进程(优点:多线程、多进程可以有效的利用CPU资源。缺点:代价就是多进程的大量内存开销,多线程的上下文切换及创建线程本身的开销). 另一种解决办法JDK中的NIO,减少线程上下文的切换。在一个线程里处理多个事情,但是会导致CPU忙于应付轮询,这是低效的。

 

posted @ 2010-03-23 11:11  highriver  阅读(4000)  评论(0编辑  收藏  举报