硬件上下文切换
每个进程都拥有自己的内存空间,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忙于应付轮询,这是低效的。
同步异步指的是通信模式,而阻塞和非阻塞指的是在接收和发送时是否等待动作完成才返回所以不能混淆这四个词。
以下是我的一些理解,请大家多指教
首先是通信的同步,主要是指客户端在发送请求后,必须得在服务端有回应后才发送下一个请求。所以这个时候的所有请求将会在服务端得到同步
其次是通信的异步,指客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求,这样对于所有的请求动作来说将会在服务端得到异步,这条请求的链路就象是一个请求队列,所有的动作在这里不会得到同步的。
阻塞和非阻塞只是应用在请求的读取和发送。
在实现过程中,如果服务端是异步的话,客户端也是异步的话,通信效率会很高,但如果服务端在请求的返回时也是返回给请求的链路时,客户端是可以同步的,这种情况下,服务端是兼容同步和异步的。相反,如果客户端是异步而服务端是同步的也不会有问题,只是处理效率低了些。
同步=阻塞式,异步=非阻塞式
同步和异步都只针对于本机SOCKET而言的
同步模式下,比如RECIEV和SEND,都要确保收到或发送完才返回,继续执行下面的代码不然就阻塞在哪里,所以,同步模式下,一般要用到线程来处理。
异步模式就不同了,不管有没有收到或发送出去,他都马上返回,继续执行下面的代码,结果又消息通知。
套接字模式
Windows套接字在两种模式下执行I/O操作:锁定和非锁定(阻塞和非阻塞)。
在锁定模式下,在I/O操作完成前,执行操作的Winsock函数(比如send和recv)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下,Winsock函数无论如何都会立即返回。
对于处在锁定模式的套接字,我们必须多加留意,因为在一个锁定套接字上调用任何一个WinsockAPI函数,都会产生相同的后果—耗费或长或短的时间“等待”。大多数Winsock应用都是遵照一种“生产者-消费者”模型来编制的。在这种模型中,应用程序需要读取(或写入)指定数量的字节,然后以它为基础执行一些计算。这种方式下的使用,一定要注意到阻塞作用产生的副作用,例如,我们编写了了一个“服务器端”的进程,创建一个套接字,然后在主线程中用一个循环接受客户端发起的连接请求,我们用到了ACCEPT函数,那么在阻塞模式下,当没有客户端请求发送时,调用accept函数的线程(这里是主线程)将一直阻塞下去,不会返回,这也就意味着你其他的并发操作无法执行,例如你的程序带有GUI界面,那么你将无法操作窗口上的其他按钮。
为了解决上述问题,我们注意到阻塞的作用是针对调用它的线程,也就是说,如果我们在主线程中创建一个辅助线程来进行轮循操作,那么虽然此辅助线程可能被阻塞,但不会影响到主线程的工作。
对锁定套接字来说,它的一个缺点在于:应用程序很难同时通过多个建好连接的套接字通信。使用前述的办法,我们可对应用程序进行修改,令其为连好的每个套接字都分配一个读线程,以及一个数据处理线程。尽管这仍然会增大一些开销,但的确是一种可行的方案。唯一的缺点便是扩展性极差,以后想同时处理大量套接字时,恐怕难以下手。
非锁定模式
除了锁定模式,我们还可考虑采用非锁定模式的套接字。尽管这种套接字在使用上存在着些许难度,但只要排除了这项困难,它在功能上还是非常强大的。除具备锁定套接字已有的各项优点之外,还进行了少许扩充,功能更强。将一个套接字置为非锁定模式之后,WinsockAPI调用会立即返回。大多数情况下,这些调用都会“失败”,并返回一个WSAEWOULDBLOCK错误。什么意思呢?它意味着请求的操作在调用期间没有时间完成。举个例子来说,假如在系统的输入缓冲区中,尚不存在“待决”的数据,那么recv(接收数据)调用就会返回WSAEWOULDBLOCK错误。通常,我们需要重复调用同一个函数,直至获得一个成功返回代码。
由于非锁定调用会频繁返回WSAEWOULDBLOCK错误,所以在任何时候,都应仔细检查所有返回代码,并作好“失败”的准备。许多程序员易犯的一个错误便是连续不停地调用一个函数,直到它返回成功的消息为止。
锁定和非锁定套接字模式都存在着优点和缺点。其中,从概念的角度说,锁定套接字更易使用。但在应付建立连接的多个套接字时,或在数据的收发量不均,时间不定时,却显得极难管理。而另一方面,假如需要编写更多的代码,以便在每个Winsock调用中,对收到一个WSAEWOULDBLOCK错误的可能性加以应付,那么非锁定套接字便显得有些难于操作。在这些情况下,可考虑使用“套接字I/ O模型”,它有助于应用程序通过一种异步方式,同时对一个或多个套接字上进行的通信加以管理。
unitUnit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,Forms,
Dialogs,winsock,StdCtrls, SkinCaption, WinSkinData;
type
TForm1 = class(TForm)
ckbxB:TCheckBox;
Memo1:TMemo;
Button1:TButton;
Button2:TButton;
SkinData1:TSkinData;
SkinCaption1: TSkinCaption;
procedureButton1Click(Sender: TObject);
procedureButton2Click(Sender: TObject);
private
{ Privatedeclarations }
public
{ Publicdeclarations }
end;
TSockReadThread=class(TThread)
private
FSocket : TSocket;
FBuf : array[0..255] of Char;
FMemo : TMemo;
procedure GetResult;
protected
procedure Execute;override;
public
constructor Create(pSocket : TSocket;mm : TMemo);
end;
var
Form1: TForm1;
WSAData : TWSAData;
implementation
{$R*.dfm}
procedureStartUp;
var
ErrorCode : integer;
begin
//加载winSock dll
ErrorCode := WSAStartup($0101, WSAData);
if ErrorCode <> 0 then
begin
ShowMessage('加载失败');
exit;
end;
end;
//创建一个服务器
procedure TForm1.Button1Click(Sender: TObject);
var
ErrorCode,AddSize : integer;
SockAdd_In,Add: TSockAddrIn;
tm : Longint;
WSAData : TWSAData;
FSock,AcceptSock : TSocket;
begin
//创建一个使用TCP协议的套接字
FSock := socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if FSock = SOCKET_ERROR then
begin
showmessage(Format('%s;ErrorCode:%d',['套接字创建失败',WSAGetLastError]));
Exit;
end;
//根据一个TCheckBox控件的选择情况来决定使用锁定模式还是非锁定模式
if ckbxB.Checked then
tm := 1 //非锁定模式
else tm := 0; //锁定模式
ioctlsocket(FSock,FIONBIO,tm);
SockAdd_In.sin_family := PF_INET;
SockAdd_In.sin_port := htons(5151);
SockAdd_In.sin_addr.S_addr := htonl(INADDR_ANY);
//绑定
ErrorCode := bind(FSock,SockAdd_In,sizeof(SockAdd_In));
if ErrorCode = SOCKET_ERROR then
begin
showmessage(Format('%s;ErrorCode:%d',['绑定失败:',WSAGetLastError]));
Exit;
end;
//置为监听模式
listen(FSock,5);
//用一个循环来反复判断是否有客户端请求,如果存在请求就创建一个用来接受数据的读取线程
while true do
begin
AddSize :=sizeof(Add);
AcceptSock:= accept(FSock,@Add,@AddSize);
ifAcceptSock <> INVALID_SOCKETthen
TSockReadThread.Create(AcceptSock,Memo1);
Application.ProcessMessages;
end;
end;
{ TSockReadThread}
constructorTSockReadThread.Create(pSocket: TSocket; mm: TMemo);
begin
FMemo := mm;
FSocket := pSocket;
inherited Create(false);
end;
procedureTSockReadThread.Execute;
var
ret : integer;
FdSet : TFDSet;
TimeVal : TTimeVal;
begin
inherited;
FreeOnTerminate := True;
while not terminated do
begin
{ FD_ZERO(FdSet);
FD_SET(FSocket,FdSet);
TimeVal.tv_sec := 0;
TimeVal.tv_usec := 500;
if(select(0,@fdSet,nil,nil,@TimeVal) > 0) and
not terminated then
begin}
ret := recv(FSocket,fbuf,256,0);
if ret > 0 then Synchronize(GetResult)
else Break;
// end;
end;
end;
procedureTSockReadThread.GetResult;
begin
FMemo.Lines.Add(FBuf);
end;
//创建客户端,并发送数据
procedure TForm1.Button2Click(Sender: TObject);
var
ErrorCode : integer;
buf : array[0..10] of Char;
SockAdd_Inc : TSockAddrIn;
SkC : TSocket;
begin
skc := socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if skc = SOCKET_ERROR then
begin
showmessage('创建失败');
Exit;
end;
SockAdd_Inc.sin_family := PF_INET;
SockAdd_Inc.sin_port := htons(5151);
SockAdd_Inc.sin_addr.S_addr := inet_addr(pchar('127.0.0.1'));
//连接
connect(skc,SockAdd_Inc,sizeof(SockAdd_Inc));
//发送数据
buf :='wudi_1982';
send(SkC,buf,10*sizeof(char),0);
//断开连接
shutdown(skc,SD_SEND);
closesocket(skc);
end;
initialization
StartUp;
finalization
WSACleanup;
end.
在上面的例子中,首先通过点击一个按钮创建一个服务器,如果选择的是阻塞模式,你可以发现程序就想“死”了一样,这是阻塞作用产生的效果,因为上面例子调用accept函数的地方是在主线程中,而此时没有客户端发起连接,因此accept将无法返回,主线程被阻塞。这种情况下,你根本无法点击那个用来创建客户端并发送数据的按钮。然后再此执行程序,使用非阻塞模式,你会看到程序执行成功,创建客户端按钮可以执行。如果有兴趣,最好在两种模式下使用单步执行,来看以下效果,主要是关产accept函数执行的情况。当然,你还可以把用来接受客户端请求的那段代码封装到一个线程中去做,例如上面例子的读取线程。
select模型
select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理!最初设计该模型时,主要面向的是某些使用Unix操作系统的计算机,它们采用的是Berkeley套接字方案。select模型已集成到Winsock1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。由于Winsock1.1向后兼容于Berkeley套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。
利用select函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入数据。之所以要设计这个函数,唯一的目的便是防止应用程序在套接字处于锁定模式中时,在一次I/O绑定调用(如send或recv)过程中,被迫进入“锁定”状态;同时防止在套接字处于非锁定模式中时,产生WSAEWOULDBLOCK错误。除非满足事先用参数规定的条件,否则select函数会在进行I/O操作时锁定。select的函数原型如下:
intselect (
intnfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const structtimeval FAR * timeout
);
其中,第一个参数nfds会被忽略。之所以仍然要提供这个参数,只是为了保持与早期的Berkeley套接字应用程序的兼容。大家可注意到三个fd_set参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据(exceptfds)。从根本上说,fd_set数据类型代表着一系列特定套接字的集合。其中,readfds集合包括符合下述任何一个条件的套接字:
■有数据可以读入。
■ 连接已经关闭、重设或中止。
■ 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
■ 有数据可以发出。
■ 如果已完成了对一个非锁定连接调用的处理,连接就会成功。
最后,exceptfds集合包括符合下述任何一个条件的套接字:
■假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
■ 有带外(OOB)数据可供读取。
例如,假定我们想测试一个套接字是否“可读”,必须将自己的套接字增添到readfds集合,再等待select函数完成。select完成之后,必须判断自己的套接字是否仍为readfds集合的一部分。若答案是肯定的,便表明该套接字“可读”,可立即着手从它上面读取数据。在三个参数中(readfds、writefds和exceptfds),任何两个都可以是空值(NULL);但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;否则,select函数便没有任何东西可以等待。最后一个参数timeout对应的是一个指针,它指向一个timeval结构,用于
决定select最多等待I/O操作完成多久的时间。如timeout是一个空指针,那么select调用会无限期地“锁定”或停顿下去,直到至少有一个描述符符合指定的条件后结束。对timeval结构的
定义如下:
timeval = record
tv_sec:Longint;
tv_usec:Longint;
end;
其中,tv_sec字段以秒为单位指定等待时间;tv_usec字段则以毫秒为单位指定等待时间。若将超时值设置为( 0 ,0),表明select会立即返回,允许应用程序对select操作进行“轮询”。出于对性能方面的考虑,应避免这样的设置。select成功完成后,会在fd_set结构中,返回刚好有未完成的I/O操作的所有套接字句柄的总量。若超过timeval设定的时间,便会返回0。不管由于什么原因,假如select调用失败,都会返回SOCKET_ERROR。用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外fd_set结构。将一个套接字分配给任何一个集合后,再来调用select,便可知道一个套接字上是否正在发生上述的I/ O活动。
例如上面的例程,在阻塞模式下,我们将while循环调用accept的那段代码做如下修改,执行一下,你会发现在阻塞模式下,刚才无法完成的动作现在可以了。
var
FdSet : TFDSet;
TimeVal : TTimeVal;
...
begin
//前面的代码不便
while true do
begin
FD_ZERO(FdSet);
FD_SET(FSock,FdSet);
TimeVal.tv_sec := 0;
TimeVal.tv_usec := 500;
//使用select函数
if (select(0,@fdSet,nil,nil,@TimeVal) > 0)then
begin
AddSize :=sizeof(Add);
AcceptSock := accept(FSock,@Add,@AddSize);
if AcceptSock <> INVALID_SOCKET then
TSockReadThread.Create(AcceptSock,Memo1);
end;
Application.ProcessMessages; end;
end;
linux内核编程(驱动)
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
上面这些概念都是教科书的概念,下面谈谈个人的理解。所谓同步就是当一个进程发起一个函数(任务)调用的时候,一直会到函数(任务)完成。进程继续往下执行。而异步这不会这样,异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件。等方式通知进程任务完成。
而阻塞和非阻塞的概念相对明了多了。阻塞是当请求不能满足的时候就试进程挂起,非阻塞则是直接返回。
它们的组合:(网络装载)
图 2 给出了传统的阻塞 I/O 模型,这也是目前应用程序中最为常用的一种模型。其行为非常容易理解,其用法对于典型的应用程序来说都非常有效。在调用 read 系统调用时,应用程序会阻塞并对内核进行上下文切换。然后会触发读操作,当响应返回时(从我们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。然后应用程序就会解除阻塞(read 调用返回)。
从应用程序的角度来说,read 调用会延续很长时间。实际上,在内核执行读操作和其他工作时,应用程序的确会被阻塞。
PS. 我理解这里的意思是,read请求是阻塞的,也没有异步通知机制,因为应用程序一直在这个过程中等待,即一直在主动查询,所以是同步的。(顺序)
同步非阻塞 I/O
同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操作不会立即完成,read 操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK),如图 3 所示。
非阻塞的实现是 I/O 命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成。这可能效率不高,因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,或者试图执行其他工作。正如图 3 所示的一样,这个方法可以引入 I/O 操作的延时,因为数据在内核中变为可用到用户调用 read 返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低。
PS. 我理解这里的意思是,read请求是非阻塞的,但是这里没有异步通知机制,而需要应用程序主动查询,所以是同步的。(多次试探)
异步阻塞 I/O
另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。
图 4. 异步阻塞 I/O 模型的典型流程 (select)
select 调用的主要问题是它的效率不是非常高。尽管这是异步通知使用的一种方便模型,但是对于高性能的 I/O 操作来说不建议使用。
PS. 我理解这里的意思是,read请求实际上是非阻塞的,但是在异步通知方式上,采用了阻塞的slect系统调用,导致应用程序被阻塞,所以虽然异步,但任然阻塞。(等待通知,自己完成)
异步非阻塞 I/O(AIO)
最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明 read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。
在一个进程中为了执行多个 I/O 请求而对计算操作和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差异。当一个或多个 I/O 请求挂起时,CPU 可以执行其他任务;或者更为常见的是,在发起其他 I/O 的同时对已经完成的 I/O 进行操作。
PS. 我理解这里的意思是,read请求实际上是非阻塞的,但是在异步通知方式上,采用了回调函数,无需应用程序再去处理。(委托完成)
异步 I/O 的动机
从前面 I/O 模型的分类中,我们可以看出 AIO 的动机。阻塞模型要求在 I/O 操作开始时阻塞应用程序,这意味着不可能同时重叠进行处理和 I/O 操作。同步非阻塞模型允许处理和 I/O 操作重叠进行,但是这需要应用程序根据重现的规则来检查 I/O 操作的状态。这样就剩下异步非阻塞 I/O 了,它允许处理和 I/O 操作重叠进行,包括 I/O 操作完成的通知。
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1 老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。
所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
程序员写程序,很多情况下,老张和水壶都要涉及,即调用者与被调用者。四种组合模式各有所长。
这个例子和银行排队有异曲同工之妙,可是我觉得这个会比较容易理解一些。
老张一次比一次聪明,我承认跟老张很像。刚开始的情况逻辑比较简单,但效率低下。随着慢慢的提高,效率也提高了。所以个人认为,程序的效率和简单程度是成反比的。
但不是说异步非阻塞就一定是最好的,有的情况下更适合使用同步或者阻塞。