阻塞、非阻塞与异步、同步(转载+整理)

来源:互联网 发布:js cron 生成器插件 编辑:程序博客网 时间:2024/05/16 09:19

同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。
阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。
阻塞是使用同步机制的结果,非阻塞则是使用异步机制的结果。

 

 

 

 

 

 

 

IO -同步,异步,阻塞,非阻塞(亡羊补牢篇)

当你发现自己最受迎的一篇blog错时这绝对不是一件人愉悦的事。
IO - 同步,异步,阻塞,非阻塞》是我在开始学epolllibevent候写的,主要的思路来自于文中的那篇link写完之后发现很多人都很喜,我是非常开心的,也问题了很多人。随着学的深入,渐渐的感原来的理解有些偏差,但是是没引起自己的重着都是一些小错误,无大雅。直到有位博友了一个问题,我重新查阅了一些更威的料,才发现原来的文章中有很大的理论错误。我不知道有多少人已过这blog并受到了我的误导,鄙人在此表示抱歉。俺以后写技blog会更加严谨的。
一度想把原文了,最后是没舍得。竟每篇blog都花了不少心血,另外放在那里也可以引以戒。所以里新一篇。算是亡羊牢吧。


同步(synchronous IO和异步(asynchronous IO,阻塞(blocking IO和非阻塞(non-blockingIO什么,到底有什么区问题不同的人出的答案都可能不同,比如wiki,就认为asynchronousIOnon-blocking IO是一个西。是因不同的人的知背景不同,并且在讨论这问题候上下文(context)也不相同。所以,了更好的回答问题,我先限定一下本文的上下文。
本文讨论的背景是Linux境下的network IO
本文最重要的参考文献是Richard StevensUNIX® Network ProgrammingVolume 1, Third Edition: The Sockets Networking6.2I/OModelsStevens这节详细说明了各种IO的特点和区,如果英文好的,推荐直接阅读Stevens的文是有名的深入浅出,所以不用担心看不懂。本文中的流程也是截取自参考文献。

 

Stevens在文章中一共比了五种IOModel
    blocking IO
    nonblocking IO
    IO multiplexing
    signal driven IO
    asynchronous IO
由于signal driven IO实际中并不常用,所以我只提及剩下的四种IO Model

一下IO涉及的象和步
于一个network IO (里我read),它会涉及到两个系统对象,一个是IOprocess (or thread),另一个就是系内核(kernel)。当一个read操作,它会经历两个段:
 1
等待数据准(Waiting for the data to be ready)
 2
将数据从内核拷程中 (Copying the data from the kernel to theprocess)
两点很重要,因为这IO Model的区就是在两个段上各有不同的情况。

 

blockingIO
linux中,默情况下所有的socket都是blocking,一个典型的操作流程大概是这样

当用户进用了recvfrom个系统调用,kernel就开始了IO的第一个段:准数据。network io,很多候数据在一开始没有到达(比如,没有收到一个完整的UDP包),kernel就要等待足的数据到来。而在用户进这边,整个程会被阻塞。当kernel一直等到数据准好了,它就会将数据从kernel中拷到用内存,然后kernel返回果,用户进程才解除block的状,重新运行起来。
所以,blocking IO的特点就是在IO行的两个段都被block了。

 

non-blockingIO

linux下,可以通过设socket使其变为non-blocking。当一个non-blockingsocket操作,流程是子:

中可以看出,当用户进read操作,如果kernel中的数据没有准好,那么它并不会block户进程,而是立刻返回一个error。从用户进程角度,它起一个read操作后,并不需要等待,而是上就得到了一个果。用户进程判断果是一个error,它就知道数据没有准好,于是它可以再次read操作。一旦kernel中的数据准好了,并且又再次收到了用户进程的system call,那么它上就将数据拷到了用内存,然后返回。
所以,用户进程其是需要不断的主动询问kernel数据好了没有。

 

IOmultiplexing

IOmultiplexing可能有点陌生,但是如果我selectepoll,大概就都能明白了。有些地方也称IO方式eventdriven IO。我都知道,select/epoll的好就在于process就可以同时处理多个网络连接的IO。它的基本原理就是select/epollfunction会不断的轮询负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如

当用户进用了select,那么整个程会被block,而同kernel监视所有select负责socket,当任何一个socket中的数据准好了,select就会返回。候用户进程再read操作,将数据从kernel到用户进程。
blockingIO并没有太大的不同,事上,更差一些。因为这里需要使用两个systemcall (select recvfrom),而blocking IO用了一个systemcall (recvfrom)。但是,用select优势在于它可以同时处理多个connection。(多一句。所以,如果理的接数不是很高的,使用select/epollweb server不一定比使用multi-threading + blocking IOweb server性能更好,可能延迟还更大。select/epoll优势并不是接能理得更快,而是在于能理更多的接。)
IO multiplexing Model中,实际中,于每一个socket,一般都置成non-blocking,但是,如上所示,整个用process是一直被block的。只不process是被select个函数block,而不是被socket IOblock

 

AsynchronousI/O

linux下的asynchronous IO用得很少。先看一下它的流程:

户进read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会户进生任何block。然后,kernel会等待数据准完成,然后将数据拷到用内存,当一切都完成之后,kernel户进送一个signal,告read操作完成了。

 

 

到目前止,已将四个IO Model都介完了。在回过头来回答最初的那几个问题blockingnon-blocking的区在哪,synchronous IOasynchronousIO的区在哪。
先回答最简单个:blocking vs non-blocking。前面的介中其很明确的明了两者的区blocking IO会一直block对应程直到操作完成,而non-blocking IOkernel数据的情况下会立刻返回。

synchronousIOasynchronous IO的区之前,需要先出两者的定Stevens出的定(其POSIX的定)是这样子的:
    A synchronous I/O operation causes the requesting processto be blocked until that
I/O operation completes;
    An asynchronous I/O operation does not cause the requestingprocess to be blocked;

两者的区就在于synchronousIO”IO operation”候会将process阻塞。按照个定,之前所述的blocking IOnon-blocking IOIO multiplexing都属于synchronous IO。有人可能会non-blockingIO并没有被block啊。里有个非常狡猾的地方,定中所指的”IOoperation”是指真IO操作,就是例子中的recvfromsystemcallnon-blocking IOrecvfromsystem call候,如果kernel的数据没有准好,这时候不会block程。但是,当kernel中数据准好的候,recvfrom会将数据从kernel到用内存中,程是被block了,在时间内,程是被block的。而asynchronous IO不一,当IO操作之后,就直接返回再也不理睬了,直到kernel送一个信号,告诉进IO完成。在整个程中,程完全没有被block

各个IO Model的比所示:

经过上面的介,会发现non-blocking IOasynchronous IO的区别还是很明的。在non-blocking IO中,程大部分时间都不会被block,但是它仍然要求程去主check,并且当数据准完成以后,也需要程主的再次recvfrom来将数据拷到用内存。而asynchronous IO完全不同。它就像是用户进程将整个IO操作交了他人(kernel)完成,然后他人做完后信号通知。在此期,用户进程不需要去检查IO操作的状,也不需要主的去拷数据。

最后,再几个不是很恰当的例子来四个IO Model:
ABCD四个人在钓鱼
A
用的是最老式的竿,所以呢,得一直守着,等到了再拉杆;
B
竿有个功能,能够显示是否有,所以呢,B就和旁MM聊天,隔会再看看有没有,有的就迅速拉杆;
C
用的竿和B差不多,但他想了一个好法,就是同放好几根竿,然后守在旁,一旦有说鱼了,它就将对应竿拉起来;
D
是个有人,干脆雇了一个人帮他钓鱼,一旦那个人把鱼钓上来了,就D个短信

 

 

 

IO-同步,异步,阻塞,非阻塞

本文在理上存在重大错误,个人所有受到此文误导的朋友表示道歉。新做一篇IO - 同步,异步,阻塞,非阻塞(亡羊补牢篇),希望能得到解。

 

 

同步(synchronous IO异步(asynchronous IO阻塞(blocking IO非阻塞(non-blockingIO,我相信几个扰过很多人,更痛苦的是,如果你查阅过文献料,你会发现不同的料中的解是不一的,例如在wiki中,异步和非阻塞被当成了一个概念
现这种情况的原因,我认为很大程度上是因IO个概念本身就很泛,它其包含了好几个面。比如,你可以把它看做是一个物理上的设备,也可以看做是 OS抽象出来的一个件,可以看做是平写程序用的read(),write()函数,不同的几个的理解也是不一的。

先看一个低的次。如果从CPU的角度看,其大部分的IO都是异步的:因CPU动这IO操作后,就去干其它的事情了,一直到生一个中断,告IO完成了。
“Mostphysical I/O is asynchronous—the CPU starts the transfer and goes off to dosomething else until the interrupt arrives. User programs are much easier towrite if the I/O operations are blocking—after a read system call the programis automatically suspended until the data are available in the buffer. It is upto the operating system to make operations that are actually interrupt-drivenlook blocking to the user programs.”(引自 Modern Operating Systems, 2ed

,本文并不想探究那么底东东。作程序,更多的是从面来考。所以,以下重点介的是用程序中能采用的四种IO机制。
明,下文中片引用自http://www.ibm.com/developerworks/cn/linux/l-async/

首先,从最常用到的,也是最容易理解的同步阻塞IO起。

个模型中,用程序(applicationread操作,会用相的一个system call,将系控制kernel,然后就行等待(就是被阻塞了)。kernel开始system call行完后会向用程序返回响用程序得到响后,就不再阻塞,并行后面的工作。

例如, read统调用程序会阻塞并内核行上下文切。然后会触发读操作,当响返回(从我正在从中取的设备中返回),数据就被移到用冲区中。然后用程序就会解除阻塞(read用返回)。


 
一个浅的例子,就好比你去一个行柜台存。首先,你会将存子填好,然后交里,你就好比是application子就是用的 system call,柜就是kernel。提交好子后,你就坐在柜台前等,相当于开始行等待。柜员办好以后会你一个回,表示好了,就是 response。然后你就可以拿着回干其它的事了。注意,候,如果你完之后上去查账,存的打到你的账户上了。后面你会发现点很重要。

接下来同步非阻塞IO
先看


linux下,用程序可以通过设置文件描述符的属性O_NONBLOCKI/O操作可以立即返回,但是并不保I/O操作成功。
也就是,当用程序置了O_NONBLOCK之后,write操作,用相system callsystemcall会从内核中立即返回。但是在个返回的时间点,数据可能没有被真正的写入到指定的地方。也就是kernel只是很快的返回了 system call这样用程序不会被IO操作blocking),但是system call具体要行的事情(写数据)可能并没有完成。而用程序,IO操作很快就返回了,但是它并不知道IO操作是否真的成功了,如果想知道,需要用程序主地去kernel

次不是去行存,而是去款。同的,你也需要填写然后交,柜员进行一些简单的手续处理就能够给你回。但是,你拿到回并不意味着打到了方的上。事上,一般款的周期大概是24个小,如果你要以存的模式来款的,意味着你需要在行等24个小这显然是不现实的。所以,同步非阻塞IO实际生活中也是有它的意的。

再来谈谈异步阻塞IO
linux中,常常通select/poll实现这种机制。

图为例,
和之前一用程序要read操作,因此用一个systemcallsystemcall传递给kernel。但在用程序这边,它systemcall之后,并不等待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 程。


比如行存在某行新开通了一钱业务。用之需要将存款柜台,然后无需等待就可以离开了。柜台好以后会户发送一条短信,告知交易成功。这样不需要在柜台前长时间的等待,同,也能得到确切的消息知道交易完成。


从前面的介中可以看出,所的同步和异步,在里指的是applicationkernel的交互方式。如果application不需要等待 kernel的回,那么它就是异步的。如果application提交完IO求后,需要等待,那么它就是同步的。
而阻塞和非阻塞,指的是application是否等待IO操作的完成。如果application等到IO操作实际完成以后再行下面的操作,那么它是阻塞的。反之,如果不等待IO操作的完成就开始行其它操作,那么它是非阻塞的

同步/异步阻塞/非阻塞的区别

我喜欢用自己的语言通过联系现实生活中的一些现象解释一些概念,当我能做到这一点时,说明我已经理解了这个概念.今天要解释的概念是:同步/异步阻塞/非阻塞的区别.

这两组概念常常让人迷惑,因为它们都是涉及到IO处理,同时又有着一些相类似的地方.

首先来解释同步和异步的概念,这两个概念与消息的通知机制有关.

举个例子,比如我去银行办理业务,可能选择排队等候,也可能取一个小纸条上面有我的号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务了.
前者(排队等候)就是同步等待消息,而后者(等待别人通知)就是异步等待消息.在异步消息处理中,等待消息者(在这个例子中就是等待办理业务的人)往往注册一个回调机制,在所等待的事件被触发时由触发机制(在这里是柜台的人)通过某种机制(在这里是写在小纸条上的号码)找到等待该事件的人.
而在实际的程序中,同步消息处理就好比简单的read/write操作,它们需要等待这两个操作成功才能返回;而异步处理机制就是类似于select/poll之类的多路复用IO操作,当所关注的消息被触发时,由消息触发机制通知触发对消息的处理.

其次再来解释一下阻塞和非阻塞,这两个概念与程序等待消息(无所谓同步或者异步)时的状态有关.
继续上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息之外不能做其它的事情,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行.相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的,因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待.但是需要注意了,第一种同步非阻塞形式实际上是效率低下的,想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;而后者,异步阻塞形式却没有这样的问题,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换.

很多人会把同步和阻塞混淆,我想是因为很多时候同步操作会以阻塞的形式表现出来,比如很多人会写阻塞的read/write操作,但是别忘了可以对fd设置O_NONBLOCK标志位,这样就可以将同步操作变成非阻塞的了;同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞,比如如果用select函数,当select返回可读时再去read一般都不会被阻塞,就好比当你的号码排到时一般都是在你之前已经没有人了,所以你再去柜台办理业务就不会被阻塞.

可见,同步/异步阻塞/非阻塞是两组不同的概念,它们可以共存组合,也可以参见这里:
http://www.ibm.com/developerworks/cn/linux/l-async/

----------------------------------------- 分割线 ------------------------------------------------------

昨晚写完这篇文章之后,今早来看了看反馈,同时再自己阅读了几遍,发现还是有一些地方解释的不够清楚,在这里继续补充完善一下我的说法,但愿没有越说越糊涂.

同步和异步:上面提到过,同步和异步仅仅是关于所关注的消息如何通知的机制,而不是处理消息的机制.也就是说,同步的情况下,是由处理消息者自己去等待消息是否被触发,而异步的情况下是由触发机制来通知处理消息者,所以在异步机制中,处理消息者和触发机制之间就需要一个连接的桥梁,在我们举的例子中这个桥梁就是小纸条上面的号码,而在select/poll等IO多路复用机制中就是fd,当消息被触发时,触发机制通过fd找到处理该fd的处理函数.

请注意理解消息通知和处理消息这两个概念,这是理解这个问题的关键所在.还是回到上面的例子,轮到你办理业务这个就是你关注的消息,而去办理业务就是对这个消息的处理,两者是有区别的.而在真实的IO操作时,所关注的消息就是该fd是否可读写,而对消息的处理就是对这个fd进行读写.同步/异步仅仅关注的是如何通知消息,它们对如何处理消息并不关心,好比说,银行的人仅仅通知你轮到你办理业务了,而如何办理业务他们是不知道的.

而很多人之所以把同步和阻塞混淆,我想也是因为没有区分这两个概念,比如阻塞的read/write操作中,其实是把消息通知和处理消息结合在了一起,在这里所关注的消息就是fd是否可读/写,而处理消息则是对fd读/写.当我们将这个fd设置为非阻塞的时候,read/write操作就不会在等待消息通知这里阻塞,如果fd不可读/写则操作立即返回.

很多人又会问了,异步操作不会是阻塞的吧?已经通知了有消息可以处理了就一定不是阻塞的了吧?
其实异步操作是可以被阻塞住的,只不过通常不是在处理消息时阻塞,而是在等待消息被触发时被阻塞.比如select函数,假如传入的最后一个timeout参数为NULL,那么如果所关注的事件没有一个被触发,程序就会一直阻塞在这个select调用处.而如果使用异步阻塞的情况,比如aio_*组的操作,当我发起一个aio_read操作时,函数会马上返回不会被阻塞,当所关注的事件被触发时会调用之前注册的回调函数进行处理,具体可以参见我上面的连接给出的那篇文章.回到上面的例子中,如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发,也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;但是呢,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下(注册一个回调函数),那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了.

 

原创粉丝点击