關於FastAPI異步併發的技術背景和細節

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章