2012年(7)
分类: LINUX
2012-04-12 21:04:35
同步非阻塞IO
在linux下,应用程序可以通过设置文件描述符的属性O_NONBLOCK,I/O操作可以立即返回,但是并不保证I/O操作成功。也就是说,当应用程序设置了O_NONBLOCK之后,执行write操作,调用相应的system call,这个system call会从内核中立即返回。但是在这个返回的时间点,数据可能还没有被真正的写入到指定的地方。也就是说,kernel只是很快的返回了这个 system call(这样,应用程序不会被这个IO操作blocking),但是这个system call具体要执行的事情(写数据)可能并没有完成。而对于应用程序,虽然这个IO操作很快就返回了,但是它并不知道这个IO操作是否真的成功了,如果想知道,需要应用程序主动地去问kernel。这次不是去银行存钱,而是去银行汇款。同样的,你也需要填写汇款单然后交给柜员,柜员进行一些简单的手续处理就能够给你回执。但是,你拿到回执并不意味着钱已经打到了对方的账上。事实上,一般汇款的周期大概是24个小时,如果你要以存钱的模式来汇款的话,意味着你需要在银行等24个小时,这显然是不现实的。所以,同步非阻塞IO在实际生活中也是有它的意义的。异步阻塞IO和之前一样,应用程序要执行read操作,因此调用一个system call,这个system call被传递给了kernel。但在应用程序这边,它调用system call之后,并不等待kernel返回response,这一点是和前面两种机制不一样的地方。这也是为什么它被称为异步的原因。但是为什么称其为阻塞呢?这是因为虽然应用程序是一个异步的方式,但是select()函数会将应用程序阻塞住,一直等到这个system call有结果返回了,再通知应用程序。也就是说,“在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。”所以,从IO操作的实际效果来看,异步阻塞IO和第一种同步阻塞IO是一样的,应用程序都是一直等到IO操作成功之后(数据已经被写入或者读取),才开始进行下面的工作。异步阻塞IO的好处在于一个select函数可以为多个描述符提供通知,提高了并发性。关于提高并发性这点,我们还以银行为例说明。比如说一个银行柜台,现在有10个人想存钱。按照现在银行的做法,一个个排队。第一个人先填存款单,然后提交,然后柜员处理,然后给回执,成功后再轮到下一个人。大家应该都在银行排过对,这样的流程是很痛苦的。如果按照异步阻塞的机制,10个人都填好存款单,然后都提交给柜台,提交完之后所有的10个人就在银行大厅等待。这时候会专门有个人,他会了解存款单处理的情况,一旦有存款单处理完毕,他会将回执交给相应的正在大厅等待的人,这个拿到回执的人就可以去干其他的事情了。而前面提到的这个专人,就对应于select函数。异步非阻塞IO
如图所示,应用程序提交read请求的system call,然后,kernel开始处理相应的IO操作,而同时,应用程序并不等kernel返回响应,就会开始执行其他的处理操作(应用程序没有被IO操作所阻塞)。当kernel执行完毕,返回read的响应,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。比如银行存钱。现在某银行新开通了一项存钱业务。用户之需要将存款单交给柜台,然后无需等待就可以离开了。柜台办好以后会给用户发送一条短信,告知交易成功。这样用户不需要在柜台前进行长时间的等待,同时,也能够得到确切的消息知道交易完成。从前面的介绍中可以看出,所谓的同步和异步,在这里指的是application和kernel之间的交互方式。如果application不需要等待 kernel的回应,那么它就是异步的。如果application提交完IO请求后,需要等待“回执”,那么它就是同步的。而阻塞和非阻塞,指的是application是否等待IO操作的完成。如果application必须等到IO操作实际完成以后再执行下面的操作,那么它是阻塞的。反之,如果不等待IO操作的完成就开始执行其它操作,那么它是非阻塞的。