FastAPI 異步代碼、並發和並行


作者:麥克煎蛋   出處:https://www.cnblogs.com/mazhiyong/ 轉載請保留這段聲明,謝謝!

 

我們這里探討下關於異步代碼、並行和並發的一些概念。

一、初探

1、如果我們使用必須用await調用的第三方庫,例如:

results = await some_library()

那么我們就要用async def來定義路徑操作函數:

@app.get('/')
async def read_results():
    results = await some_library()
    return results

注意:我們在基於async def定義的函數內部才能使用await

2、如果第三方庫不支持使用await,那么我們就用def定義路徑操作函數即可。

@app.get('/')
def results():
    results = some_library()
    return results

3、如果我們的應用不需要與第三方通訊,那么就用async def來定義路徑操作函數。

4、如果我們不知道怎么做,那么就用def來定義路徑操作函數。

無論上述哪種情況,FastAPI都會執行異步工作並且速度極快。

但如果我們遵循一些操作規范,將會帶來一些性能上的優化。

 

現代版本的Python通過使用"協程"來實現對"異步代碼"的支持,在語法上表現為asyncawait的使用。

我們以下重點講述的內容為:

  • 異步代碼
  • asyncawait
  • 協程

二、異步代碼

異步代碼通常表示,開發語言有一種方式用來通知計算機(應用)在代碼的某個地方,必須等待某些事件在其他地方完成。

這里的某些事件我們稱之為"slow-file"。在等待"slow-file"完成的這個時間段內,計算機可以執行一些別的任務。

然后計算機(應用)一旦有機會就會返回,比如它需要再次等待,或者它完成了在這個地方的所有其他任務。

接下來會檢查所有等待的任務是否已經完成,或者繼續執行應當要完成的任務。

然后它會從等待任務中取走第一個任務繼續執行。

這里等待的某些事件通常指的是,相對程序計算或者內存操作比較耗時的I/O操作,例如

  • 網絡通訊
  • 硬盤文件讀寫
  • 遠程 API 操作
  • 數據庫操作
  • 其他耗時操作

之所以被稱為"異步"是因為計算機(應用)沒必要為了"同步"等待"slow-file"完成而什么事情都不做,那樣的話只能等待取到任務結果后才能繼續工作。

實際上,作為一個異步系統,一旦某些事件完成,這個事件會等待一會以便計算機(應用)返回獲取結果,然后利用執行結果繼續工作。

對於同步系統來說,通常也稱之為"順序模型",因為在切換執行一個不同任務的時候,計算機(應用)嚴格遵循序列中的步驟,即使有些步驟包含了等待。

2.1 並發漢堡

以上討論的異步代碼有時候也稱之為"並發",這與"並行"是不同的。

"並發"和"並行"都意味着"不同的事情或多或少在相同的時間發生",但它們的細節是非常不同的。

你和你的朋友去吃快餐,你排隊的時候收銀員按順序為在你之前的顧客點餐。
輪到你的時候,你為自己和朋友點了兩份新潮的漢堡。
然后你付錢。
然后收銀員告訴廚師以便他准備你的漢堡(雖然他可能正在為其他顧客准備漢堡)。
收銀員給你的訂單號。

在等待取餐的時候,你和朋友挑選一個桌子坐下,然后你們交流了很長時間(制作新潮的漢堡比較耗時)。
在和朋友愉快交流的時候,時不時的,你會看一下櫃台是否顯示了你的訂單號。

在某個時間終於輪到你了,你去櫃台取回你的漢堡,然后返回到座位和你的朋友分享。
---------------------------------------------------------------------------
在這個故事里,你可以把自己想象成計算機(應用)。

當你排隊的時候你是空閑的,沒有做什么有效的工作。但隊伍是很快的,因為收銀員僅僅是收銀和下單。
輪到你的時候,你做了一些有效的工作,你查看菜單和決定點菜內容,然后支付並檢查支付結果,同時確認返回的訂單內容是正確的。
然后,雖然你還沒得到漢堡,但是你和收銀員之間的工作處於"暫停"狀態,因為你不得不等待漢堡制作完成。

雖然你離開了收銀台,帶着一個號碼返回到了桌子旁,不過你可以把你的注意力切換到你的朋友身上,繼續你們的交流"工作"。
然后你有了一些"有效的"工作,那就是增進你和朋友之間的感情。

當收銀員說漢堡准備好了並且把你的訂單號放在顯示屏上的時候,你並不會立刻跳起來沖過去,因為你知道沒人偷你的漢堡,這是你的訂單號,別人有別人的訂單號。
因此你會等待你的朋友講完她的故事(結束當前的工作),然后微笑着告訴她你要去取漢堡。

然后你來到櫃台(繼續你最開始的任務),取到漢堡,感謝收銀員,然后返回到座位。現在終於結束了與櫃台之間的交互任務。
按照順序,現在開啟了一個新的任務,"吃漢堡",但前一個任務"取漢堡"已經完成了。

2.2 並行漢堡

現在我們來看一下什么是並行漢堡。

你和你的朋友去獲取並行快餐。

你排隊的時候,同時有幾個(暫且認為有8個)收銀員在為顧客下單,這幾個收銀員同時也是廚師。
每個在你前面的顧客必須等待取到漢堡才能離開櫃台,因為這8個收銀員在為下一個顧客服務之前,必須立刻去准備好當前顧客的漢堡。

輪到你的時候,你下單兩個新潮的漢堡。
你完成支付。
然后收銀員去廚房制作。
你在櫃台前等待着,這樣就不會有別人能取走你的漢堡,因為並沒有根據訂單號取貨。
這樣在你和你的朋友忙於不讓別人插隊和取走你的漢堡的時候,你們並沒有多余的精力進行交流。

這是一種"同步"工作,你和收銀員(廚師)之間處於同步狀態。你必須在那里等到收銀員(廚師)制作完成漢堡然后交給你,否則別人就可能會取走你的漢堡。
經過在櫃台前的長時間等待后,收銀員(廚師)終於帶着你的漢堡回來了。
你取到漢堡然后返回餐桌和朋友一起就餐。
你享用漢堡。完成你的漢堡任務。

因為在櫃台前大量的等待時間,你並沒有與你的朋友有很好的交流。
---------------------------------------------------------------------------
在並發漢堡的場景里,你是一個帶有兩個處理器(你和你的朋友)的計算機(應用),同時在櫃台前等待了很長時間。
快餐店有8個處理器(收銀員/廚師)。與之相比並發快餐店只有兩個處理器(一個收銀員,一個廚師)。
但最終,並發漢堡的體驗仍然不是最好的。

 下面是一個與漢堡相同的並行故事。

直到最近,大部分的銀行還是有多個收銀員和一個長長的隊伍。
所有的收銀員都是處理完當前顧客的所有事情后,才會開始服務下一位。
你不得不在隊伍里長時間等待,否則你就會失去你的機會。

2.3 漢堡結論:

在"與朋友一起吃快餐漢堡"的場景里,因為有許多時間在等待,這就為並發系統帶來了更多意義。

這也是大多數web應用的常見情景。 許多許多用戶都在等待通過他們不太好的網絡來發送請求,然后又等待請求結果的返回。 這種單次的"等待"雖然是毫秒級的,但把它們加起來,最終就導致了大量的等待。
這也是在web應用中使用異步代碼的實際意義

許多流行的Python框架(包含Flask和Django)是在Python的新異步特性存在之前創建的,因此它們對異步特性的支持並不像最新的特性那么給力。

異步特性造就了NodeJS的流行,同時也是Go語言的立足所在。現在我們通過FastAPI框架也能獲得同樣的性能水平。

2.4 並發比並行更好嗎?

不是這樣的,這並不是這個故事的寓意所在。

並發與並行是不同的。只有在某些特定的場景下(比如包含大量的等待)它是較好一些的。

通常對於web應用來說,並發是比並行更好一些的。但這並不是全部。

想象一下下面這個小故事:

你要打掃一個又大又臟的房間!

對了!這才是全部的故事內容!

在這個房間的所有地方,不存在需要等待的事情,只有大量的工作需要完成。
你可以像漢堡范例那樣按順序執行,先打掃起居室,然后打掃廚房,但因為不存在等待,只是不停的打掃和打掃,因此打掃的順序不會有任何影響。
無論是否按照順序或者不按照順序(並發)打掃,你需要完成的全部工作量是相同的,你所花費的全部工時也是相同的。

但是在這種情況下,如果你能帶來8個人(以前叫收銀員/廚師,現在叫清潔工人),每個人(和你一起)各自負責打掃房間的一塊區域,你們就能並行的完成所有的工作,並且更加快速。
這個時候,每一個清潔者(包括你自己)就是一個處理器,各自完成各自負責的工作。

因為大部分的執行時間被實際工作所占據,並且在計算機中實際工作是被CPU所完成的,我們通常稱這類問題為"CPU bound",也稱之為計算密集型。
---------------------------------------------------------------------------
計算密集型的操作通常涉及到復雜的數學計算。
例如多媒體的處理、機器視覺、機器學習或者深度學習等

我們可以這樣簡單理解並發與並行:

你吃飯吃到一半,電話來了,你一直到吃完了以后才去接,這就說明你不支持並發也不支持並行。
你吃飯吃到一半,電話來了,你停了下來接了電話,接完后繼續吃飯,這說明你支持並發。
你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支持並行。
並發的關鍵是你有處理多個任務的能力,不一定要同時。
並行的關鍵是你有同時處理多個任務的能力。 

2.5 並發 + 並行: web和機器學習

借助FastAPI,我們通常可以在web開發中充分利用並發的優勢。

但對於計算密集型的工作(比如機器學習)我們也可以利用到並行和多處理器的優勢。

尤其考慮到Python是數據計算、機器學習以及深度學習的主要語言,這也使得FastAPI非常適用於數據計算/機器學習的web API和應用開發。

三、async and await

現代版本的Python用一種非常直觀的方式來定義異步代碼。看起來就像通常的"順序"代碼在某一時刻進行"等待"操作。

當有一個操作需要等待執行結果的時候,代碼范例如下:

burgers = await get_burgers(2)

關鍵之處在於使用了await。這里告訴Python必須等待get_burgers(2)執行完,才能把結果存儲到burgers中。

借助上述語法,Python將會在這個期間內去執行別的操作(比如接收新的請求)。

 

await必須在一個支持異步特性的函數內進行使用,也就是說必須是用async def聲明的函數:

async def get_burgers(number: int): # Do some asynchronous stuff to create the burgers
    return burgers

而不是用def聲明的函數:

# This is not asynchronous
def get_sequential_burgers(number: int): # Do some sequential stuff to create the burgers
    return burgers

 

借助async def,Python知道在這樣的函數內,必須要注意await表達式。它可以在結果返回前,"暫停"這個函數的執行,先去執行別的任務。

當你調用async def函數的時候,你也必須等待(await)它,否則函數將不會執行。

# This won't work, because get_burgers was defined with: async def
burgers = get_burgers(2)

因此,當我們使用的第三方庫聲明要使用await調用的時候,我們必須要創建一個基於async def聲明的路徑操作函數,例如:

@app.get('/burgers')
async def read_burgers(): burgers = await get_burgers(2) return burgers

相關技術細節

我們注意到了await操作必須是在async def函數內使用,async def函數也必須在另一個async def函數內使用。

這就像雞生蛋和蛋生雞的問題,我們怎么調用第一個async函數呢?

在FastAPI框架內部我們不用擔心這個問題,因為第一個函數就是我們的路徑操作函數,而FastAPI框架會正確處理這個問題。

在FastAPI框架外關於 async/await 的詳細使用,我們可以參考文檔 check the official Python docs

四、關於協程

對於async def函數返回的操作,我們有一個非常花式的術語稱之為"協程"。

Python知道這個操作可以像函數一樣啟動和完成,並且也可以在內部暫停執行,只要存在await操作。

通過asyncawait完成的異步代碼通常稱為"協程",這是相對比於Go語言的主要特性,其稱之為"Goroutines"。

五、其他技術細節

5.1 路徑操作函數

當直接通過def聲明一個路徑操作函數的時候,它會運行在一個外部的線程池里並且處於等待狀態,而不是被直接調用(這樣往往會阻塞住整個server)。

如果我們以前使用的是與上述工作方式不同的異步框架,並且習慣了直接定義def函數來獲取微小的性能提升(也許是100納秒),請注意在FastAPI中

這種效果是完全不同的。在這種情況下,我們最好用async def來聲明函數除非路徑操作函數在執行I/O阻塞操作。

無論在哪種情況下,FastAPI都會比你以前所用的框架表現更好一些(或者至少是持平)。

5.2 依賴項

如果依賴項函數是def而不是async def定義的,那么它也運行在外部的線程池中。

你可能有多個依賴項和子依賴項相互依賴,一些是async def定義的,一些是def定義的。這仍然會正常工作。def定義的會在外部線程中被調用。

5.3 工具函數

其他直接調用的工具類函數,無論是async def定義或者是def定義,FastAPI不會影響你調用的方式。

這與FastAPI為你調用的函數是截然不同的:包括了路徑操作函數和依賴項函數。

如果你的工具類函數是def定義的,那么它會被直接調用,而不會運行在任何線程池中;如果是async def定義的,那么當你調用的時候應當使用await操作。

 

參考文章:

https://fastapi.tiangolo.com/async/ 

 

 

 

 



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM