FastAPI的路徑操作函數,可以使用async def
定義:
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
這算得上是FastAPI的典型特徵之一。
關於這個框架設計,有哪些技術背景和細節呢?
技術背景
在Python語法裏面,如果你想異步請求三方庫,需要使用await:
results = await some_library()
使用了await就必須在def前面加上async:
@app.get('/')
async def read_results():
results = await some_library()
return results
這是Python語法規定。
FastAPI並不要求所有的路徑操作函數,都必須定義爲async,假如你要實時訪問某些三方庫,可以簡單的使用def就行,不用加上await:
@app.get('/')
def results():
results = some_library()
return results
但是無論你是否使用async,FastAPI都將異步工作,以達到"Fast"的運行速度。
看完文章就明白這句話的意思了。
技術細節
Python新版本已經原生支持異步代碼了。所謂異步代碼,指的是編程語言,會告訴計算機程序,在某個時刻停下來,等待其他任務完成後,再繼續運行。在等待期間,計算機程序可以去幹點別的事情,而不用一直卡在那裏。這些“其他任務”,通常指的是耗時較長的IO操作,比如:
-
客戶端通過網絡發送數據;
-
服務端通過網絡發送數據;
-
程序從磁盤讀取文件內容;
-
程序將文件內容寫入磁盤;
-
遠程API操作;
-
數據庫操作;
-
數據庫查詢返回結果;
這些操作主要阻塞在IO等待,所以又叫做IO密集型。
併發和並行
異步有時候也叫做併發。併發(Concurrency)和並行(parallelism)是不同的概念,併發是指一個處理器同時處理多個任務,並行是指多個處理器同時處理多個不同的任務,併發是邏輯上的同時發生,並行是物理上的同時發生。
併發
餐廳有1個服務員和1個廚子。
你帶女朋友到餐廳排隊點漢堡:
你們給服務員說來2個漢堡:
服務員給廚師說做2個漢堡:
然後給了你們一個排號:
你們開心的等,因爲有排號,不需要擔心別人會搶走:
叫號了:
取了漢堡高高興興的喫:
並行
餐廳有5個服務員兼廚子。
你們看哪個窗口有空位:
到餐檯點2個漢堡:
服務員自己跑到廚房做漢堡:
你們只能站在原地等,如果走開,可能會被其他人拿走:
漢堡做好了:
你的女朋友不開心:
從這個買漢堡的漫畫中,可以看到並行比並發會做更多無意義的等待,並行需要5個人(5個服務員兼廚子),併發只需要2個人(1個服務員1個廚子)。這就是爲什麼很多Web框架要設計成異步併發了,因爲很多客戶端會發請求給服務端,然後服務端響應給客戶端,如果有太多無用的等待,那麼整個應用將慢得無法使用。而且硬件資源有限,併發也能更高效利用資源,節約成本。
併發一定就比並行好嗎?也不是,只有在出現很多等待時,併發才比並行好。比如你們要打掃房間,一間一間的打掃,沒有等待,那麼併發和並行就沒有區別,如果你再叫3個朋友一起打掃,並行就能更快打掃完。這種執行時間完全取決於任務本身而不是等待的情況,又叫做CPU密集型。計算機裏的CPU密集型操作通常需要更復雜的數據計算,比如:
-
音頻或圖片處理;
-
計算機視覺;
-
機器學習;
-
深度學習;
FastAPI既支持異步併發,也支持多線程並行。
async和await
異步併發使用async和await來實現。
async
定義函數:
async def get_burgers(number: int):
# Do some asynchronous stuff to create the burgers
return burgers
await
調用函數:
@app.get('/burgers')
async def read_burgers():
burgers = await get_burgers(2)
return burgers
細節中的細節
FastAPI會對路徑操作函數(path operation function)和依賴(dependencies)進行特殊處理。這個特殊處理是:如果你把函數定義爲def而不是async def,那麼FastAPI會把它放到單獨的線程池中,異步執行,這就是FastAPI精彩的地方。就像官方所說,如果你不清楚你函數裏面的調用是不是異步(能不能用await),那麼就把它定義爲普通函數,FastAPI會採用多線程的方式處理。亂用async,在async裏面有同步調用,則會變成串行,Fast秒變Slow。
而對於其他函數,FastAPI則不會管,def就是同步調用,立馬返回結果。
現在回過頭來看前面的那句話:但是無論你是否使用async,FastAPI都將異步工作,以達到"Fast"的運行速度。應該更加明白了。
參考資料:
Concurrency and async / await - FastAPI https://fastapi.tiangolo.com/async/
很火的Fastapi框架,用async函數真的比普通函數快嗎?https://blog.csdn.net/yyw794/article/details/108859240