run_in_executor 会阻止吗?

Will run_in_executor ever block?

假设我有这样的 Web 服务器:

from fastapi import FastAPI
import uvicorn
import asyncio

app = FastAPI()

def blocking_function():
    import time
    time.sleep(5)
    return 42

@app.get("/")
async def root():
    loop = asyncio.get_running_loop()

    result = await loop.run_in_executor(None, blocking_function)
    return result

@app.get("/ok")
async def ok():
    return {"ok": 1}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", workers=1)

据我了解,代码将在默认的 ThreadExecutorPool 中生成另一个线程,然后在线程池中执行阻塞函数。另一方面,想想GIL是如何工作的,CPython解释器只会执行一个线程100ticks然后它会切换到另一个线程来公平地给其他线程一个机会进步。在这种情况下,如果 Python 解释器决定切换到 blocking_function 正在执行的线程怎么办?它会阻止 who 解释器等待 time.sleep(5) 上剩余的任何内容吗?

我问这个的原因是我观察到有时我的应用程序会在 blocking_function 上阻塞,但是我不完全确定这里发生了什么,因为我的 blocking_function 很特别 - - 它通过 win32com 库与 COM API 对象对话。我试图排除这是我陷入的一些 GIL 陷阱。

两者time.sleep(如this question) and the win32com library (according to this mailing list post中所述)都在调用时释放GIL,因此它们不会阻止其他线程在阻塞时取得进展。

回答“high-level”问题 - “run_in_executor 是否可以(直接或间接)阻止 event-loop?” - 如果您使用 ThreadPoolExecutor 并且您在 run_in_executor 中执行的代码执行了未释放 GIL 的阻塞工作,则答案只会是“是”。虽然这不会完全阻塞事件循环,但这意味着您的事件循环线程和执行程序线程不能并行 运行,因为两者都需要获取 GIL 才能取得进展。