非同步調用和單線程,多線程的疑惑?

最近在看非同步調用的資料,發現很多資料都說的是:非同步調用指的就是無法立刻得知返回結果,而是通過「狀態」「通知」和「回調」來得知結果。這裡的回調是如何返回結果的?

會不會是這樣子,就是比如一個滑鼠點擊事件的響應,框架比如cocos2dx就是用回調來實現的,這個算不算非同步調用,應該是非同步調用(不然如果是同步調用的話,我不點擊滑鼠,那程序就一直等在那裡,不會往下運行了)。那既然是非同步調用,同時它(滑鼠點擊事件的響應)是一個單線程,非多線程,那也就是說非同步回調和多線程單線程沒有關係是嗎?既可以用多線程來做非同步調用,也可以用單線程來實現非同步調用?不知道我這樣的理解對不對?如果是這樣,那「非同步」這個詞豈不是一個很廣的概念,並不是和多線程聯繫在一起的?

還有,就是當今做界面的框架比如mfc,qt,它們的滑鼠點擊事件響應,是不是也是用回調來實現的?是單線程還是多線程?


回調不等同於非同步,不回調也不等同於同步。

下面開始一堆概念:多進程、多線程、微線程(協程)都是為了實現並行化而出現的解決方案;同步與非同步是並行執行中協作的形式;回調是一種比較便於並行化以及非同步化的表現邏輯;並行化中對共用資源的使用存在阻塞與非阻塞之分。

這事得從調度說起:CPU這玩意,時至今日,一個核(嗯講道理超線程應該拆成核來看)還是只跑一路流程,一條指令隊列的。但是畢竟要做的事情往往不止一件,並且需要同時(至少看起來同時)做,於是就出現了進程,一種由OS和CPU協同的『時分復用』形式上的擬並行化。簡單的說,進程/線程切換時,寄存器等各種運行時上下文先存起來,然後讀取另一個進程的上下文,執行一段後再轉。進程/線程調度是以CPU來實現的,從代碼的角度來說,你沒辦法控制它啥時候進行切換。

而微線程則是本質上的單線程,而用代碼來實現上下文切換,這就需要代碼指明允許進行切換的地方;若是沒有指明,則會一直執行下去進而出現資源阻塞。對於如同js/node的非同步、python的協程、erlang的微線程,其實都是解釋器(運行時)保持了一個事件隊列,所有的非同步調用都不過是在事件隊列里新加事件,回調等也不過是加事件的形式。換句話說,如果你的代碼里沒有任何允許切換上下文到點(沒有外部非同步調用,沒有顯式yield/await),它就不會自動切換線程。

對於Qt、Cocos2dx等,信號槽/事件機制與微線程非常相似,但是由於c++沒有給你個原生非同步語法,若你不自己創建線程,又沒有庫內的非同步調用,那麼你的函數就是獨佔了當前線程資源在跑的,其它的回調就沒法跑了。

至於阻塞,是在並行化已經跑起來之後,如果一項資源A在用完前B只能等著,這叫阻塞;A還沒用完,不過在等待,期間允許B用它,這叫非阻塞。

最後總結一下:回調並行在nodejs、python async、erlang中之所以可以並行,是因為解釋器/運行時做了相應的工作;回調實現的並行是通過代碼控制的並行;多線程、多進程的並行是OS+CPU調度的並行,不需要加額外的東西也能表現出並行化;回調、協程之類的並行,是代碼管理的偽並行化,是需要手動切換的(至少指明可以切換的地方),你若不給機會,它就切不動。


先搞清楚阻塞非阻塞、同步非同步這幾個概念:

阻塞等於同步,非阻塞等於非同步這種說法有什麼錯誤? - 可知不可知的回答

再看回調函數:

In computer programming, a callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at some convenient time. The invocation may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback.

單線程和多線程我就不解釋了、什麼是線程什麼是進程也不解釋了,題主應該清楚吧。

如上所述,回調,其實就是把一個函數作為參數傳遞給另一個函數;回調可以是同步的也可以是非同步的;同步非同步和單線程多線程沒有關係。或者說,同步也可以是單線程也可以是多線程;非同步則必須是多線程或者多進程(每個進程可以是單線程)。

========== 請確保到此為止沒有任何問題,如果有問題,就先不要往下看 ===========

事件機制,必然用到回調函數,有的回調是同步的,有的回調是非同步的。

如果我沒記錯的話,C語言的printf回調了相應的函數,這個過程是同步的(比如你在某個地方printf里大量數據,那麼程序就會卡在這裡,一直到所有的數據都print了才繼續往後走);javascript的print是非同步的(不確定,記不清了)。

同步回調就是:把函數b傳遞給函數a。執行a的時候,回調了b,a要一直等到b執行完才能繼續執行;

非同步回調就是:把函數b傳遞給函數a。執行a的時候,回調了b,然後a就繼續往後執行,b獨自執行。---請注意這裡不要和多線程糾纏起來:線程是存在於一個進程里的,而非同步回調可能是好幾個進程。

另外,滑鼠點擊的event不一定是非同步的但一定是非阻塞的

比如所有的event都對應一個消息循環機制(main event loop),當我有一個event(比如滑鼠點擊)時,就觸發一個消息,然後該消息就回調一個函數,而這個被回調的函數要執行完才會繼續往下走---這時就是同步的。

為什麼一定是非阻塞的呢?很好理解,如果是阻塞的,那麼假如某個event的回調函數需要執行1分鐘,那麼在這1分鐘之內,起他event就都不能被處理了。

當然,對於非阻塞的main event loop,我們可以用單進程多線程來實現,但至於是不是必須用非同步回調,這個不一定。

while(true) // main event loop ----- 一定是非阻塞的
{
為滑鼠event創建一個線程---回調函數a---函數a不執行完這個線程就一直等著(同步回調)
為輸出流創建一個線程---回調函數b---函數b執行同時線程繼續往後執行(非同步回調)
}


事件一定是回調(當然這個回調中可以開新線程);

回調是模塊化的基礎,沒有回調根本沒辦法模塊化.

非同步的缺點,只有一個線程,只能在一個cpu上跑,比如你想同時算 兩個大計算量的函數的和,本來可以分別在2個cpu上算,然後求和,現在只能在一個cpu上算.

多線程的缺點,線程切換廢資源.

所以兩者用在符合自身的場景.多線程:運算密集型場景,非同步:此外的場景.


瀉藥。

第一次在知乎上回答問題,有說的不好的地方,希望大家多多指教。

我所知道的回調函數有:c/c++的函數指針,js的函數型參數,c++javagolang這類面向對象語言中的「繼承"(golang沒有繼承)和"介面」。

回調函數最終讓別人調用的,既然是別人調用,自己就不知道什麼時候會被調用。就像去找領導簽字一樣,自己不知道領導何時有空,只能告訴領導的秘書自己的電話,然後讓領導主動來調用。

題主的問題比較多,我就挑第一個問題回答吧:「這裡的回調是如何返回結果的?」 ---回調的形參

舉個栗子:

//回調函數
//data可以認為就是返回結果
function callback(data){
alert("Data: " + data);
}
//訪問網路
//結束後調用callback
$.get("http://xxx.com/getdata.php",callback);

$.get函數訪問xxx.com/getdata.php,自己並不知道什麼時候數據返回,當數據返回(或者出錯)的時候callback函數被調用,形參data就是返回的結果。至於$.get函數內部是如何把data傳給callback:

//回調函數
//data可以認為就是返回結果
function callback(data){
alert("Data: " + data);
}
//訪問網路
//結束後調用callback
function myGet(url,callbk){
var data="@#$@$DS%#%^#@"; //網路返回的結果
callbk(data); //myGet調用callblk,把data給callbk形參
}

myGet("http://xxxx.com",callback);

至於myGet調用callback,callback是如何訪問形參。對於c語言來講,形參放在函數調用棧ebp之前,具體可以參照編譯原理。

此外,回調函數不僅用來得到一些無法立即返回的結果,比如各種重繪函數ondraw,這是GUI系統調用重繪,並非用來得到一些結果:

比如Android里:

public class SwitchButton extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//你的重繪邏輯
}
}

這裡的onDraw是繼承自View,自己寫的代碼裡面,通篇沒有主動調用onDraw函數,但這個函數卻會被GUI調用,canvas就是調用者傳遞的參數。onDraw也算是一種回調函數,但是你卻不需要手動去註冊這個函數,這也是面向對象(繼承函數)和面向過程(函數指針)一個特點(優點)吧。


推薦閱讀:

Qt 多線程串口通信問題?
linux線程是如何進行切換的?
在不改變方法簽名(method signature)的情況下, 請描述這段代碼的問題以及如何解決?
測試線程同步中出現的阻塞問題?
多線程執行順序控制?

TAG:非同步 | 回調函數Callback | C | 多線程 | 單線程 |