我有以下代碼:
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/ping")
async def ping(request: Request):
print("Hello")
time.sleep(5)
print("bye")
return {"ping": "pong!"}
如果我在本地服務器上運行我的代碼,例如,http://localhost:8501/ping在同一個 Firefox 視窗的不同選項卡中,我會得到:
Hello
bye
Hello
bye
...
代替:
Hello
Hello
bye
bye
我已經閱讀了有關使用 httpx 的資訊,但仍然無法實作真正??的并行化。有什么問題?
uj5u.com熱心網友回復:
根據FastAPI 的檔案:
def當您使用 normal而不是宣告路徑操作函式時async def,它會在等待的外部執行緒池中運行,而不是直接呼叫(因為它會阻塞服務器)。
因此,def(同步)路由在執行緒池的單獨執行緒中運行,或者換句話說,服務器同時處理請求,而async def路由在主(單個)執行緒上運行,即服務器按順序處理請求 - 只要因為在此類路由中沒有await呼叫I/O-bound操作,例如等待來自客戶端的資料通過網路發送,要讀取磁盤中檔案的內容,完成資料庫操作等 - 看看這里. 如果是這樣,Python 的 GIL會自動釋放并交給另一個執行緒(請求)。但是,這不適用于CPU-bound操作 - 請參見此處(CPU-bound操作,即使在async def函式并呼叫 using await,將阻塞事件回圈)。time.sleep()這也意味著路由中的阻塞操作(例如 )async def將阻塞整個服務器(如您的情況)。async使用和await的異步代碼多次被總結為使用協程。
因此,如果您的函式不打算進行任何async呼叫,則應def改為宣告它,如下所示:
@app.get("/ping")
def ping(request: Request):
#print(request.client)
print("Hello")
time.sleep(5)
print("bye")
return "pong"
否則,如果您要呼叫必須呼叫的async函式,await則應使用async def關鍵字。為了證明這一點,下面使用asyncio.sleep()了asyncio庫中的函式。這里也給出了類似的例子(你也可以看看這個答案)。
import asyncio
@app.get("/ping")
async def ping(request: Request):
print("Hello")
await asyncio.sleep(5)
print("bye")
return "pong"
如您的問題中所述,以上都將列印預期的輸出。
注意:當您第二次(第三次等)呼叫端點時,請記住從與瀏覽器主會話隔離的選項卡中執行此操作,否則,請求將顯示為來自同一客戶端(您print(request.client)如果兩個選項卡都在同一個視窗中打開,則可以檢查 using - 埠號是否相同),因此,請求是按順序處理的。您可以重新加載相同的選項卡(正在運行),或者在隱身視窗中打開一個新選項卡,或者使用另一個瀏覽器/客戶端發送請求。
異步/等待和長計算(CPU 系結操作)
If you are required to use async def keyword (as you might need to await for coroutines), but also have some synchronous long computation task that might be blocking the server and doesn't let other requests to go through, for example:
@app.post("/ping")
async def ping(file: UploadFile = File(...)):
print("Hello")
contents = await file.read()
some_long_computation_task(contents) # this blocks other requests
print("bye")
return "pong"
then:
Use more workers (e.g.,
uvicorn main:app --workers 4). Note: Each worker "has its own things, variables and memory". This means thatglobalvariables/objects, etc., won't be shared across the processes/workers. In this case, you should consider using a database storage, or Key-Value stores (Caches), as described here and here. Additionally, "if you are consuming a large amount of memory in your code, each process will consume an equivalent amount of memory".Use FastAPI's (Starlette's)
run_in_threadpool(), which will run your task in a separate thread. As described by @tiangolo here, "run_in_threadpoolis an awaitable function, the first parameter is a normal function, the next parameters are passed to that function directly. It supports sequence arguments and keyword arguments".from fastapi.concurrency import run_in_threadpool response = await run_in_threadpool(some_long_computation_task, contents)Use
asyncios'srun_in_executordirectly:loop = asyncio.get_running_loop() response = await loop.run_in_executor(None, lambda: some_long_computation_task(contents))You should also check if you could change your route's definition to
def. For example, if the only method in your endpoint that has to be awaited is the one for reading the file contents (as you mentioned in the comments section), FastAPI can read thebytesof a file for you (however, this should work for small files, as the whole contents will be stored in memory, see here), or you could even call theread()method of theSpooledTemporaryFileobject directly, so that you don't have to await theread()method - and since you can now declare your route withdef, each request will run in a separate thread.@app.post("/ping") def ping(file: UploadFile = File(...)): print("Hello") contents = file.file.read() some_long_computation_task(contents) print("bye") return "pong"Have a look at this answer, as well as the documentation here, for more suggested solutions.
uj5u.com熱心網友回復:
問:
“……有什么問題?”
答:
FastAPI 檔案明確表示該框架使用行程內任務(繼承自Starlette)。
就其本身而言,這意味著所有此類任務都在競爭(不時地)接收 Python 解釋器 GIL 鎖 - 有效地成為 MUTEX-terrorising 全域解釋器鎖,它實際上重新[SERIAL]定義了任何和所有數量的 Python解釋行程內執行緒
以作為一個且唯一的作業- 而所有其他人 - 等待......
在細粒度范圍內,您會看到結果——如果為第二個(從第二個 FireFox-tab 手動啟動)到達的 http-request 生成另一個處理程式實際上需要比睡眠時間更長的時間,這是 GIL 鎖交錯~ 100 [ms]時間的結果-quanta round-robin (all-wait-one-can-work ~ 100 [ms]before each next round GIL-lock release-acquire-roulette 發生) Python Interpreter 內部作業不顯示更多細節,您可以使用更多細節(取決于 O /S type or version ) 從這里查看更多執行緒內 LoD,就像在執行的異步修飾代碼中一樣:
import time
import threading
from fastapi import FastAPI, Request
TEMPLATE = "INF[{0:_>20d}]: t_id( {1: >20d} ):: {2:}"
print( TEMPLATE.format( time.perf_counter_ns(),
threading.get_ident(),
"Python Interpreter __main__ was started ..."
)
...
@app.get("/ping")
async def ping( request: Request ):
""" __doc__
[DOC-ME]
ping( Request ): a mock-up AS-IS function to yield
a CLI/GUI self-evidence of the order-of-execution
RETURNS: a JSON-alike decorated dict
[TEST-ME] ...
"""
print( TEMPLATE.format( time.perf_counter_ns(),
threading.get_ident(),
"Hello..."
)
#------------------------------------------------- actual blocking work
time.sleep( 5 )
#------------------------------------------------- actual blocking work
print( TEMPLATE.format( time.perf_counter_ns(),
threading.get_ident(),
"...bye"
)
return { "ping": "pong!" }
最后但并非最不重要的一點是,不要猶豫,閱讀更多關于所有其他基于鯊魚執行緒的代碼可能遭受...甚至導致...幕后...
Ad Memorandum :
a mixture of GIL-lock, thread-based pools, asynchronous decorators, blocking and event-handling -- a sure mix to uncertainties & HWY2HELL
;o)
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/448916.html
