www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導讀]以前寫過一篇關于回調函數(shù)的文章C語言函數(shù)指針之回調函數(shù),今天又安排了一篇。


大家好,以前寫過一篇關于回調函數(shù)的文章C語言函數(shù)指針之回調函數(shù),今天又安排了一篇。

不知你是不是也有這樣的疑惑,我們?yōu)槭裁葱枰卣{函數(shù)這個概念呢?直接調用函數(shù)不就可以了?回調函數(shù)到底有什么作用?程序員到底該如何理解回調函數(shù)?

這篇文章就來為你解答這些問題,讀完這篇文章后你的武器庫將新增一件功能強大的利器。

一切要從這樣的需求說起

假設你們公司要開發(fā)下一代國民App“明日油條”,一款主打解決國民早餐問題的App,為了加快開發(fā)進度,這款應用由A小組和B小組協(xié)同開發(fā)。 其中有一個核心模塊由A小組開發(fā)然后供B小組調用,這個核心模塊被封裝成了一個函數(shù),這個函數(shù)就叫make_youtiao()。 如果make_youtiao()這個函數(shù)執(zhí)行的很快并可以立即返回,那么B小組的同學只需要:

  1. 調用make_youtiao()
  2. 等待該函數(shù)執(zhí)行完成
  3. 該函數(shù)執(zhí)行完后繼續(xù)后續(xù)流程

從程序執(zhí)行的角度看這個過程是這樣的:

  1. 保存當前被執(zhí)行函數(shù)的上下文
  2. 開始執(zhí)行make_youtiao()這個函數(shù)
  3. make_youtiao()執(zhí)行完后,控制轉回到調用函數(shù)中


如果世界上所有的函數(shù)都像make_youtiao()這么簡單,那么程序員大概率就要失業(yè)了,還好程序的世界是復雜的,這樣程序員才有了存在的價值。

現(xiàn)實并不容易

現(xiàn)實中make_youtiao()這個函數(shù)需要處理的數(shù)據(jù)非常龐大,假設有10000個,那么make_youtiao(10000)不會立刻返回,而是可能需要10分鐘才執(zhí)行完成并返回。 這時你該怎么辦呢?想一想這個問題。 可能有的同學會問,和剛才一樣直接調用不可以嗎,這樣多簡單。 是的,這樣做沒有問題,但就像愛因斯坦說的那樣“一切都應該盡可能簡單,但是不能過于簡單”。 想一想直接調用會有什么問題? 顯然直接調用的話,那么調用線程會被阻塞暫停,在等待10分鐘后才能繼續(xù)運行。在這10分鐘內該線程不會被操作系統(tǒng)分配CPU,也就是說該線程得不到任何推進。 這并不是一種高效的做法。 沒有一個程序員想死盯著屏幕10分鐘后才能得到結果。 那么有沒有一種更加高效的做法呢? 想一想我們上一篇中那個一直盯著你寫代碼的老板(見《從小白到高手,你需要理解同步與異步》),我們已經(jīng)知道了這種一直等待直到另一個任務完成的模式叫做同步。 如果你是老板的話你會什么都不干一直盯著員工寫代碼嗎?因此一種更好的做法是程序員在代碼的時候老板該干啥干啥,程序員寫完后自然會通知老板,這樣老板和程序員都不需要相互等待,這種模式被稱為異步。 回到我們的主題,這里一種更好的方式是調用make_youtiao()這個函數(shù)后不再等待這個函數(shù)執(zhí)行完成,而是直接返回繼續(xù)后續(xù)流程,這樣A小組的程序就可以和make_youtiao()這個函數(shù)同時進行了,就像這樣:

在這種情況下,回調(callback)就必須出場了。

為什么我們需要回調callback

有的同學可能還沒有明白為什么在這種情況下需要回調,別著急,我們慢慢講。 假設我們“明日油條”App代碼第一版是這樣寫的:
make_youtiao(10000);sell();

可以看到這是最簡單的寫法,意思很簡單,制作好油條后賣出去。

我們已經(jīng)知道了由于make_youtiao(10000)這個函數(shù)10分鐘才能返回,你不想一直死盯著屏幕10分鐘等待結果,那么一種更好的方法是讓make_youtiao()這個函數(shù)知道制作完油條后該干什么,即,更好的調用make_youtiao的方式是這樣的: “制作10000個油條, 炸好后賣出去 ”,因此調用make_youtiao就變出這樣了:

make_youtiao(10000, sell);

看到了吧,現(xiàn)在make_youtiao這個函數(shù)多了一個參數(shù),除了指定制作油條的數(shù)量外 還可以指定制作好后該干什么 ,第二個被make_youtiao這個函數(shù)調用的函數(shù)就叫回調,callback。 現(xiàn)在你應該看出來了吧,雖然sell函數(shù)是你定義的,但是這個函數(shù)卻是被其它模塊調用執(zhí)行的,就像這樣:

make_youtiao這個函數(shù)是怎么實現(xiàn)的呢,很簡單:

void make_youtiao(int num, func call_back) { // 制作油條 call_back(); //執(zhí)行回調 }
這樣你就不用死盯著屏幕了,因為你把make_youtiao這個函數(shù)執(zhí)行完后該做的任務交代給make_youtiao這個函數(shù)了,該函數(shù)制作完油條后知道該干些什么,這樣就解放了你的程序。 有的同學可能還是有疑問,為什么編寫make_youtiao這個小組不直接定義sell函數(shù)然后調用呢? 不要忘了明日油條這個App是由A小組和B小組同時開發(fā)的,A小組在編寫make_youtiao時怎么知道B小組要怎么用這個模塊,假設A小組真的自己定義sell函數(shù)就會這樣寫:
void make_youtiao(int num) { real_make_youtiao(num); sell(); //執(zhí)行回調 }
同時A小組設計的模塊非常好用,這時C小組也想用這個模塊,然而C小組的需求是制作完油條后放到倉庫而不是不是直接賣掉,要滿足這一需求那么A小組該怎么寫呢?
void make_youtiao(int num) { real_make_youtiao(num);  if (Team_B) { sell(); // 執(zhí)行回調 } else if (Team_D) { store(); // 放到倉庫 }}
故事還沒完,假設這時D小組又想使用呢,難道還要接著添加if else嗎? 這樣的話A小組的同學只需要維護make_youtiao這個函數(shù)就能做到工作量飽滿了,顯然這是一種非常糟糕的設計。 所以你會看到,制作完油條后接下來該做什么不是實現(xiàn)make_youtiao的A小組該關心的事情,很明顯只有調用make_youtiao這個函數(shù)的使用方才知道。 因此make_youtiao的A小組完全可以通過回調函數(shù)將接下來該干什么交給調用方實現(xiàn),A小組的同學只需要針對回調函數(shù)這一抽象概念進行編程就好了,這樣調用方在制作完油條后不管是賣掉、放到庫存還是自己吃掉等等想做什么都可以,A小組的make_youtiao函數(shù)根本不用做任何改動,因為A小組是針對回調函數(shù)這一抽象概念來編程的。 以上就是回調函數(shù)的作用,當然這也是針對抽象而不是具體實現(xiàn)進行編程這一思想的威力所在。面向對象中的多態(tài)本質上就是讓你用來針對抽象而不是針對實現(xiàn)來編程的。

異步回調

故事到這里還沒有結束。 在上面的示例中,雖然我們使用了回調這一概念,也就是調用方實現(xiàn)回調函數(shù)然后再將該函數(shù)當做參數(shù)傳遞給其它模塊調用。 但是,這里依然有一個問題,那就是make_youtiao函數(shù)的調用方式依然是同步的,關于同步異步請參考《從小白到高手,你需要理解同步與異步》,也就是說調用方是這樣實現(xiàn)的:
make_youtiao(10000, sell);// make_youtiao函數(shù)返回前什么都做不了

我們可以看到,調用方必須等待make_youtiao函數(shù)返回后才可以繼續(xù)后續(xù)流程,我們再來看下make_youtiao函數(shù)的實現(xiàn):

void make_youtiao(int num, func call_back) { real_make_youtiao(num); call_back(); //執(zhí)行回調 }
看到了吧,由于我們要制作10000個油條,make_youtiao函數(shù)執(zhí)行完需要10分鐘,也就是說即便我們使用了回調,調用方完全不需要關心制作完油條后的后續(xù)流程,但是調用方依然會被阻塞10分鐘,這就是同步調用的問題所在。 如果你真的理解了上一節(jié)的話應該能想到一種更好的方法了。 沒錯,那就是異步調用 反正制作完油條后的后續(xù)流程并不是調用方該關心的,也就是說調用方并不關心make_youtiao這一函數(shù)的返回值,那么一種更好的方式是:把制作油條的這一任務放到另一個線程(進程)、甚至另一臺機器上。 如果用線程實現(xiàn)的話,那么make_youtiao就是這樣實現(xiàn)了:
void make_youtiao(int num, func call_back) { // 在新的線程中執(zhí)行處理邏輯 create_thread(real_make_youtiao, num, call_back);}

看到了吧,這時當我們調用make_youtiao時就會 立刻返回 ,即使油條還沒有真正開始制作,而調用方也完全無需等待制作油條的過程,可以立刻執(zhí)行后流程:

make_youtiao(10000, sell);// 立刻返回// 執(zhí)行后續(xù)流程
這時調用方的后續(xù)流程可以和制作油條 同時 進行,這就是函數(shù)的 異步調用 ,當然這也是異步的高效之處。

新的編程思維模式

讓我們再來仔細的看一下這個過程。 程序員最熟悉的思維模式是這樣的:
  • 調用某個函數(shù),獲取結果
  • 處理獲取到的結果
res = request();handle(res);

這就是函數(shù)的同步調用,只有request()函數(shù)返回拿到結果后,才能調用handle函數(shù)進行處理,request函數(shù)返回前我們必須 等待 ,這就是同步調用,其控制流是這樣的:

但是如果我們想更加高效的話,那么就需要異步調用了,我們不去直接調用handle函數(shù),而是作為參數(shù)傳遞給request:

request(handle);
我們根本就不關心request什么時候真正的獲取的結果,這是request該關心的事情,我們只需要把獲取到結果后該怎么處理告訴request就可以了,因此request函數(shù)可以立刻返回,真的獲取結果的處理可能是在另一個線程、進程、甚至另一臺機器上完成。 這就是異步調用,其控制流是這樣的:


從編程思維上看,異步調用和同步有很大的差別,如果我們把處理流程當做一個任務來的話,那么同步下整個任務都是我們來實現(xiàn)的,但是異步情況下任務的處理流程被分為了兩部分:
  1. 第一部分是我們來處理的,也就是調用request之前的部分
  2. 第二部分不是我們處理的,而是在其它線程、進程、甚至另一個機器上處理的。
我們可以看到由于任務被分成了兩部分,第二部分的調用不在我們的掌控范圍內,同時只有調用方才知道該做什么,因此在這種情況下回調函數(shù)就是一種必要的機制了。 也就是說回調函數(shù)的本質就是“只有我們才知道做些什么,但是我們并不清楚什么時候去做這些,只有其它模塊才知道,因此我們必須把我們知道的封裝成回調函數(shù)告訴其它模塊”。 現(xiàn)在你應該能看出異步回調這種編程思維模式和同步的差異了吧。 接下來我們給回調一個較為學術的定義

正式定義

在計算機科學中,回調函數(shù)是指一段以參數(shù)的形式傳遞給其它代碼的可執(zhí)行代碼。

這就是回調函數(shù)的定義了。
回調函數(shù)就是一個函數(shù),和其它函數(shù)沒有任何區(qū)別。 注意,回調函數(shù)是一種軟件設計上的概念,和某個編程語言沒有關系,幾乎所有的編程語言都能實現(xiàn)回調函數(shù)。 對于一般的函數(shù)來說,我們自己編寫的函數(shù)會在自己的程序內部調用,也就是說函數(shù)的編寫方是我們自己,調用方也是我們自己。 但回調函數(shù)不是這樣的,雖然函數(shù)編寫方是我們自己,但是函數(shù)調用方不是我們,而是我們引用的其它模塊,也就是第三方庫,我們調用第三方庫中的函數(shù),并把回調函數(shù)傳遞給第三方庫,第三方庫中的函數(shù)調用我們編寫的回調函數(shù),如圖所示:

而之所以需要給第三方庫指定回調函數(shù),是因為第三方庫的編寫者并不清楚在某些特定節(jié)點,比如我們舉的例子油條制作完成、接收到網(wǎng)絡數(shù)據(jù)、文件讀取完成等之后該做什么,這些只有庫的使用方才知道,因此第三方庫的編寫者無法針對具體的實現(xiàn)來寫代碼,而只能對外提供一個回調函數(shù),庫的使用方來實現(xiàn)該函數(shù),第三方庫在特定的節(jié)點調用該回調函數(shù)就可以了。 另一點值得注意的是,從圖中我們可以看出回調函數(shù)和我們的主程序位于同一層中,我們只負責編寫該回調函數(shù),但并不是我們來調用的。 最后值得注意的一點就是回調函數(shù)被調用的時間節(jié)點,回調函數(shù)只在某些特定的節(jié)點被調用,就像上面說的油條制作完成、接收到網(wǎng)絡數(shù)據(jù)、文件讀取完成等,這些都是事件,也就是event,本質上我們編寫的回調函數(shù)就是用來處理event的,因此從這個角度看回調函數(shù)不過就是event handler,因此回調函數(shù)天然適用于事件驅動編程event-driven,我們將會在后續(xù)文章中再次回到這一主題。

回調的類型

我們已經(jīng)知道有兩種類型的回調,這兩種類型的回調區(qū)別在于回調函數(shù)被調用的時機。 注意,接下來會用到同步和異步的概念,對這兩個概念不熟悉的同學可以參考上一盤文章《從小白到高手,你需要理解同步和異步》。
同步回調 這種回調就是通常所說的同步回調synchronous callbacks、也有的將其稱為阻塞式回調blocking callbacks,或者什么修飾都沒有,就是回調,callback,這是我們最為熟悉的回調方式。 當我們調用某個函數(shù)A并以參數(shù)的形式傳入回調函數(shù)后,在A返回之前回調函數(shù)會被執(zhí)行,也就是說我們的主程序會等待回調函數(shù)執(zhí)行完成,這就是所謂的同步回調。

有同步回調就有異步回調。
異步回調 不同于同步回調, 當我們調用某個函數(shù)A并以參數(shù)的形式傳入回調函數(shù)后,A函數(shù)會立刻返回,也就是說函數(shù)A并不會阻塞我們的主程序,一段時間后回調函數(shù)開始被執(zhí)行,此時我們的主程序可能在忙其它任務,回調函數(shù)的執(zhí)行和我們主程序的運行同時進行。 既然我們的主程序和回調函數(shù)的執(zhí)行可以同時發(fā)生,因此一般情況下,主程序和回調函數(shù)的執(zhí)行位于不同的線程或者進程中。

這就是所謂的異步回調,asynchronous callbacks,也有的資料將其稱為deferred callbacks ,名字很形象,延遲回調。 從上面這兩張圖中我們也可以看到,異步回調要比同步回調更能充分的利用機器資源,原因就在于在同步模式下主程序會“偷懶”,因為調用其它函數(shù)被阻塞而暫停運行,但是異步調用不存在這個問題,主程序會一直運行下去。 因此,異步回調更常見于I/O操作,天然適用于Web服務這種高并發(fā)場景。

回調對應的編程思維模式

讓我們用簡單的幾句話來總結一下回調下與常規(guī)編程思維模式的不同。 假設我們想處理某項任務,這項任務需要依賴某項服務S,我們可以將任務的處理分為兩部分,調用服務S前的部分PA,和調用服務S后的部分PB。 在常規(guī)模式下,PA和PB都是服務調用方來執(zhí)行的,也就是我們自己來執(zhí)行PA部分,等待服務S返回后再執(zhí)行PB部分。 但在回調這種方式下就不一樣了。 在這種情況下,我們自己來執(zhí)行PA部分,然后告訴服務S:“等你完成服務后執(zhí)行PB部分”。 因此我們可以看到,現(xiàn)在一項任務是由不同的模塊來協(xié)作完成的。 即:
  • 常規(guī)模式:調用完S服務后后我去執(zhí)行X任務,
  • 回調模式:調用完S服務后你接著再去執(zhí)行X任務,
其中X是服務調用方制定的,區(qū)別在于誰來執(zhí)行。

為什么異步回調越來越重要

在同步模式下,服務調用方會因服務執(zhí)行而被阻塞暫停執(zhí)行,這會導致整個線程被阻塞,因此這種編程方式天然不適用于高并發(fā)動輒幾萬幾十萬的并發(fā)連接場景, 針對高并發(fā)這一場景,異步其實是更加高效的,原因很簡單,你不需要在原地等待,因此從而更好的利用機器資源,而回調函數(shù)又是異步下不可或缺的一種機制。

回調地獄,callback hell

有的同學可能認為有了異步回調這種機制應付起一切高并發(fā)場景就可以高枕無憂了。 實際上在計算機科學中還沒有任何一種可以橫掃一切包治百病的技術,現(xiàn)在沒有,在可預見的將來也不會有,一切都是妥協(xié)的結果。 那么異步回調這種機制有什么問題呢? 實際上我們已經(jīng)看到了,異步回調這種機制和程序員最熟悉的同步模式不一樣,在可理解性上比不過同步,而如果業(yè)務邏輯相對復雜,比如我們處理某項任務時不止需要調用一項服務,而是幾項甚至十幾項,如果這些服務調用都采用異步回調的方式來處理的話,那么很有可能我們就陷入回調地獄中。 舉個例子,假設處理某項任務我們需要調用四個服務,每一個服務都需要依賴上一個服務的結果,如果用同步方式來實現(xiàn)的話可能是這樣的:
a = GetServiceA();b = GetServiceB(a);c = GetServiceC(b);d = GetServiceD(c);
代碼很清晰,很容易理解有沒有。 我們知道異步回調的方式會更加高效,那么使用異步回調的方式來寫將會是什么樣的呢?
GetServiceA(function(a){ GetServiceB(a, function(b){ GetServiceC(b, function(c){ GetServiceD(c, function(d) { .... }); }); });});
我想不需要再強調什么了吧,你覺得這兩種寫法哪個更容易理解,代碼更容易維護呢? 博主有幸曾經(jīng)維護過這種類型的代碼,不得不說每次增加新功能的時候恨不得自己化為兩個分身,一個不得不去重讀一邊代碼;另一個在一旁罵自己為什么當初選擇維護這個項目。 異步回調代碼稍不留意就會跌到回調陷阱中,那么有沒有一種更好的辦法既能結合異步回調的高效又能結合同步編碼的簡單易讀呢? 幸運的是,答案是肯定的,我們會在后續(xù)文章中詳細講解這一技術。

總結

在這篇文章中,我們從一個實際的例子出發(fā)詳細講解了回調函數(shù)這種機制的來龍去脈,這是應對高并發(fā)、高性能場景的一種極其重要的編碼機制,異步加回調可以充分利用機器資源,實際上異步回調最本質上就是事件驅動編程,這是我們接下來要重點講解的內容。

免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉