异步(Asynchronous)机制(三)--和同步机制的本质区别

来源:互联网 发布:mac ps cc破解教程 编辑:程序博客网 时间:2024/05/01 13:14

矛盾很久,不确定是否该用“本质”这个词,觉着自己好像还没资格这么说。其实,这篇探讨的是换个角度看待同步和异步的差异。

 

为了分析同步和异步的区别,还是以前两篇中出现过的Client发送request和接收response的程序为例。如果是同步机制的程序,大致应该是这样的(只是一些伪代码):

[java] view plaincopy
  1. Socket sock = connectServer(address);  
  2. if(sock == null) {  
  3.     handleConnectError();  
  4. }    
  5. boolean status = writeRequest(request);  
  6. if(!status) {  
  7.     handleWriteError();       
  8. }  
  9. response = readResponse();  
  10. if(response == null) {  
  11.     handleReadError();  
  12. else {  
  13.     handleResponse(response);  
  14. }  

可以看出,整片代码中有多个功能函数,并且他们被顺序的组合成了这一大块代码。

如果是异步的程序,写法却是这样的(也是伪代码,不过模仿的是Mina程序):

[java] view plaincopy
  1. ConnectFuture connFuture = connectServer(address);  
  2. connFuture.addListener(new ConnectListener());  
  3. private class ConnectListener implements IoFutureListener<ConnectFuture>{  
  4.               
  5.             public void operationComplete(ConnectFuture future) {  
  6.                   
  7.                 if (future.isConnected()) {  
  8.                     IoSession session = future.getSession();  
  9.                     WriteFuture writeFuture = writeRequest(request);  
  10.         writeFuture.addListener(new WriteListener());             
  11.                       
  12.                 } else {  
  13.                     handleConnectError();  
  14.                                     }  
  15.             }  
  16.         }  
  17.     }  
  18. }  
  19. private class WriteListener implements IoFutureListener<WriteFuture> {  
  20.     public void operationComplete(WriteFuture future) {  
  21.         if(future.isWritten()) {  
  22.             // do nothing  
  23.         } else {  
  24.             handleWriteError();  
  25.         }  
  26.     }  
  27. }  
  28.   
  29. public class Receiver extends IoHandlerAdapter {  
  30.     @Override  
  31.     public void exceptionCaught(IoSession session, Throwable cause) {  
  32.         handleReadError();  
  33.     }  
  34.     @Override  
  35.     public void messageReceived(IoSession session, Object message)  
  36.             throws Exception {  
  37.     handleResponse(message);  
  38.     }  
  39. }  

在这些代码中,也能够找到同步代码中出现的那些函数,所不同的是,这些函数的大部分都变成了回调,更显著的区别在于,这些函数已经不是也不能顺序的放在了同一个代码块中了。所以,在同步机制中非常完整,非常有条理的代码在异步机制中会被切割得支离破碎。这就回答了第一篇中最后的那个问题,为什么大家都不太喜欢用异步机制,因为它确实不够友好,也不太符合人一般的思维习惯。

再来琢磨琢磨这些代码在执行的时候发生了什么,这里很有趣。
先从同步的代码说起。当代码执行connectServer这个函数的时候,程序会去尝试连接远程的服务器,这期间,线程被阻塞了,因此内核将线程挂起,将线程运行时的一些context(比如寄存器)给保存在某个地方,然后运行另外一个线程,当连接成功以后,线程又被唤醒,将它的context重新恢复,然后继续运行。同样的,当运行到readResponse()的时候,内核去网卡缓冲区中读数据,这时候数据还没到,于是又将线程挂起,切换并保存context,然后运行另一个线程,当网卡缓冲区有数据后,线程被唤醒,恢复context,然后继续运行。

 

如果是异步程序呢?比如执行connectServer完后返回一个ConnectFuture,这时候连接还没有完成。于是我们向Mina注册一个回调函数ConnectListener,它告诉Mina当连接完成后要去调用writeRequest函数。这里存在一个问题,Mina怎么知道writeRequest应该向哪个socket写入数据呢?诀窍在于connectServer返回的ConnectFuture。它被当做参数传入了回调函数中,而在ConnectFuture中保存了目标server对应的Socket(就是我们代码中的那个Session)。
再比如当request被写出去后,Mina是通过Java Nio的Select来接收response的,当socket中有数据写入时,messageReceived就会被调用。但是,这个函数是如何知道message是从哪个socket中读到的呢?因为Mina会将对应的socket作为参数写入。那么,Mina又是如何知道读到的message是哪个request的呢?Mina是不会知道的,所以,正如第二篇写的那样,我们必须自己维护一个ID号,通过ID号找到对应的request。

 

好吧,我承认上面的这几段比较看着还是挺乱的。让我再把思路理顺一些。对于同步的程序,只需要一个线程顺序的执行所有的功能函数,在执行的过程中,线程一旦被阻塞,就会被内核挂起,直到它能够继续走下去,这期间,内核会负责切换线程的上下文。而对于异步程序,首先,整个代码会被切割成不同的功能函数作为回调,它们之间的执行顺序没有保证;其次,不会只有一个线程完成所有的任务,比如在第二篇的Hadoop RPC中,读和写是分别由两个线程完成的,而在Mina中,它内部也有一定数量的工作线程,上述的那些回调函数可能会被不同的线程执行;最后,即便这些回调函数被不同的线程执行,但是,在他们的运行过程中,也发生了上下文的切换,比如必须告诉ConnectListener是哪个socket连接成功了,要告诉messageReceived是从哪socket读取的数据,要告诉handleResponse这个response对应于哪个request,只是,和同步机制不同的是,这些上下文的切换是由提供异步机制的程序完成的(比如Mina,比如Hadoop RPC),而不是内核提供的。

原创粉丝点击