閱讀528 返回首頁    go 技術社區[雲棲]


同步~異步~阻塞~非阻塞

原文地址:同 步 和 異 步作者:zenos

 

一、同步~異步~阻塞~非阻塞

    同步(Synchronous)和異步(Asynchronous)的概念本來來自通信領域:首先是通信的同步,主要是指客戶端在發送請求後,必須得在服務端有回應後才發送下一個請求,所以這個時候的所有請求將會在服務端得到同步;其次是通信的異步,指客戶端在發送請求後,不必等待服務端的回應就可以發送下一個請求,這樣對於所有的請求動作來說將會在服務端得到異步,這條請求的鏈路就像是一個請求隊列,所有的動作在這裏不會得到同步的。


    阻塞(Blocking)和非阻塞(Non-blocking)主要是指某個程序模塊、進程是否占用了係統的某種資源(各種硬件資源和軟件資源,甚至包括時間等等)而影響其它程序模塊、進程無法運行或者處理它的任務。如果影響了,就是阻塞;如果沒有影響,就是非阻塞。

    很多人會把“同步/異步”和“阻塞/非阻塞”的概念搞混淆,總認為同步就是阻塞,異步就是非阻塞。從上麵的描述可以看出,其實“同步/異步”和“阻塞/非阻塞”之間是沒有任何關係的。

   

二、 同步調用~異步調用~阻塞調用~非阻塞調用

    在實際中,我們說“同步和異步”,其實往往指的是函數調用的“同步和異步”,也就是“同步調用和異步調用”。所謂同步調用,就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。按照這個定義,其實絕大多數函數都是同步調用(例如sin, isdigit等)。但是一般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。異步調用的概念和同步相對。當一個異步過程調用發出後,調用者不能立刻得到結果,但調用者會立即從被調用者返回。實際處理這個調用的部件在完成後,通過狀態、通知和回調來通知調用者。


    阻塞調用是指調用結果返回之前,當前線程會被掛起。函數隻有在得到結果之後才會返回。非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。

    這個時候又有很多人會把“同步調用和異步調用”和“阻塞調用和非阻塞調用”搞混淆,把同步調用等同於阻塞調用,把異步調用等同於非阻塞調用,其實這是不對的。


    對於同步調用來說,很多時候當前線程還是激活的,隻是從邏輯上當前函數沒有返回而已。而對於阻塞調用來說,當前線程會立即掛起,硬要等到要做的事情完成才會退出。

    對於異步調用來說,它往往隻是Assign一個Task給被調用部件,被調用部件返回的結果是接不接受調用者的Task請求(如果太忙,或者任務隊列滿等原因會導致不接受請求),如果它接受你的請求,等到它做完後會想辦法告訴調用者。而對於非阻塞調用來說,就是在調用的時候,如果被調用者能完成,就直接完成並告訴你完成了,如果它完不成就會告訴被調用者它無法完成,並告訴調用者它完不成的原因(當然,有些情況會省略這一步,一種情況是調用者不在意是什麼原因導致執行部件無法完成;還有一種情況是隻有一種原因導致執行部件無法完成,所以不說調用者也知道)。


    我們可以將這四種調用方式打幾個粗淺的比喻:


    同步調用:“一定要把這個事情搞定!要人給人,要錢給錢!” 被調用者費了九牛二虎之力後虛弱的回答: “老大,事情擺平了……”


    異步調用:“嗯,你把這個事情處理一下。”被調用者回答TRUE:“收到,放心吧,老大,搞定後我會通知你的。”回答FALSE:“老大,現在我忙死了!等我緩口氣行不行?”


    阻塞調用:“這段時間你就專心搞這個事情,不惜一切代價把這個事情搞定!等到天荒地老也要搞定!在線等!”被調用者求爺爺告奶奶四處求人終於把問題搞定後很委屈的回答:“老大,事情擺平了~~~”


    非阻塞調用:“嗯,你把這個事情處理一下。”被調用者一件一件的檢查需要辦完這件事情的資源,如果發現某種資源不夠,就回答FALSE:“老大,XXXX沒有,我搞不定。”如果整個檢查下來發現資源都有,於是做完,並很幹脆的回答TRUE:“老大,事情擺平了!”

 

三、異步調用結果的返回

    當執行部件執行任務完畢後(任務不一定成功完成),就需要將結果告訴調用者,好讓調用者進行善後處理。前麵說了,異步調用的結果,通過狀態(State)、通知(Notify)和回調(Callback)來通知調用者。這三種方法大致如下:


    狀態:當執行部件執行任務完畢後,通過設置某些全局變量來傳遞信息,被調用者在主程序中查詢這些信息,並根據這些信息作出善後處理。在我們的程序中,狀態機(State Machine)就是這種方法的典型體現。


    通知:當執行部件執行任務完畢後,通過某種通訊方法通知調用部件。在我們程序中,8032發UOP後,RISC處理完畢後的Notify就是這種方法的典型體現。


    回調:調用者在Assign Task的同時,除了任務,還會傳遞一個回調函數到執行部件,執行部件會通過函數指針記錄這個回調函數;執行部件執行任務完畢後,會通過函數指針來調用這個回調函數。在我們的程序中,播放USB中的Divx文件時,讀文件內容采用的就是這種方法。


    對於這三種方法,我們也可以打一個比較粗淺的比喻:


    狀態:領導要下屬做某件事情,下屬接過任務開始做……領導過一段時間就跑過去問:搞定沒有?回答:沒有搞定;領導過一段時間又跑過去問:搞定沒有?回答:沒有搞定……領導跑過去問:搞定沒有?回答:搞定了!


    通知:領導要下屬做某件事情,當下屬做完以後,打電話告訴領導:“我搞定了。”


    回調:領導要下屬做某件事情,在交代事情的時候,還給他一個“錦囊妙計”,當下屬做完以後,打開“錦囊妙計”,將善後事宜一一處理完畢。

    我們可以看出,狀態的方法比較簡單,也比較容易實現,並且不容易出錯,可讀性相對也好;但是它的實時性和效率沒有通知和回調的方法好。

 

四、各種調用的進一步思考

    闡述到這裏,我想我們有必要停下來思考一些東西,那就是這些不同的調用有哪些優缺點,應該應用在哪些場合?


    很多人認為異步調用比同步調用好;非阻塞調用比阻塞調用好;這也是不對的。個人一直認為:沒有最好的,隻有最適合的。同步調用和阻塞調用,程序相對簡單,而且可讀性強,移植性好,容易實現,容易維護……還有在一些特殊情況,必須使用同步調用和阻塞調用(不使用的話,其它的事情就沒有辦法做,比如初始化)。相對於同一個Task,異步調用和非阻塞調用並不能加快這個Task的執行速度和效率;相反,它們反而會降低這個Task的執行速度和效率!異步調用和非阻塞調用是站在整個係統的層麵上考慮問題的,它們的目的在於使得整個係統運行的性能效率提高,各個部件之間運行配合更加協調。這才是它們真正的優點。所以我們在想把某個模塊從同步/阻塞方式改為異步/非阻塞方式的時候就要考慮是否真的能夠達到預定的目的。

 

最後更新:2017-04-02 22:16:36

  上一篇:go myeclipse下的第一個servlet
  下一篇:go KVC與KVO的不同