我是 Python 开发的新手。 (但我有doenet背景) 我有一个简单的 FastAPI 应用程序
from fastapi import FastAPI
import time
import logging
import asyncio
import random
app = FastAPI()
r = random.randint(1, 100)
logging.basicConfig(level="INFO", format='%(levelname)s | %(asctime)s | %(name)s | %(message)s')
logging.info(f"Starting app {r}")
@app.get("/")
async def long_operation():
logging.info(f"Starting long operation {r}")
await asyncio.sleep(1)
time.sleep(4) # I know this is blocking and the endpoint marked as async, but I actually do have some blocking requests in my code.
return r
然后我使用这个命令运行应用程序:
uvicorn "main:app" --workers 4
应用程序在不同进程中启动 4 个实例:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started parent process [22112]
INFO | 2023-05-11 12:32:43,544 | root | Starting app 17
INFO: Started server process [10180]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO | 2023-05-11 12:32:43,579 | root | Starting app 58
INFO: Started server process [29592]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO | 2023-05-11 12:32:43,587 | root | Starting app 12
INFO: Started server process [7296]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO | 2023-05-11 12:32:43,605 | root | Starting app 29
INFO: Started server process [15208]
INFO: Waiting for application startup.
INFO: Application startup complete.
然后我打开 3 个浏览器选项卡并开始尽可能并行地向应用程序发送请求。这是日志:
INFO | 2023-05-11 12:32:50,770 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:32:55,774 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:33:00,772 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:33:05,770 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:33:10,790 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:33:15,779 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:33:20,799 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:33:25,814 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
INFO | 2023-05-11 12:33:30,856 | root | Starting long operation 29
INFO: 127.0.0.1:55031 - "GET / HTTP/1.1" 200 OK
我的观察:
我的问题:
- 为什么只有一个过程有效而其他过程无效?
我无法重现你的观察。事实上,我不知道你是如何推断出来的。如果我更改您的日志记录格式并添加
logging.basicConfig(level="INFO", format='%(process)d | %(levelname)s | %(asctime)s | %(name)s | %(message)s')
(注意打印进程 ID 的
%(process)d
)然后我在日志中看到
19968 | INFO | 2023-05-11 12:45:53,297 | root | Starting long operation 35
21368 | INFO | 2023-05-11 12:45:56,112 | root | Starting long operation 90
5268 | INFO | 2023-05-11 12:45:56,626 | root | Starting long operation 3
22024 | INFO | 2023-05-11 12:45:57,032 | root | Starting long operation 19
5268 | INFO | 2023-05-11 12:45:57,416 | root | Starting long operation 3
22024 | INFO | 2023-05-11 12:45:57,992 | root | Starting long operation 19
在并行产生多个请求之后。您是否有可能错误地触发了您的请求?不是并行的吗?
无论如何,所有的工人都被利用了。然而,选择它们的确切方式是一个实现细节。
- 如果我想有内存缓存。我能做到吗?
你的意思是工人之间共享?并不真地。您可以进行一些跨进程通信(例如共享内存),但这并不容易做到和维护。通常我们会为每个进程使用一个内存缓存。除非你受到记忆的限制,否则它确实成为一个问题。
- 我可以运行 1 个可以并行处理一定数量请求的进程吗?
我不确定我是否理解你的问题。如果你愿意,你可以用
--workers 1
运行 uvicorn,没问题。 Python 的默认异步运行时是单线程的,因此您不会获得真正的并行性。而是并发,类似于 JavaScript 的工作方式。因此你需要小心,你必须避免像time.sleep
这样的阻塞调用,而使用像asyncio.sleep
这样的非阻塞调用。好吧,对于异步编程,无论您生成多少个进程,您在这样做时都必须小心。
- 这是否与我在 Windows 上进行测试有关?
不,这与操作系统无关。这种设计是由于 Python 本身的主要缺陷:它具有 GIL(全局解释器锁),这使得与 dotnet/C# 等其他运行时相比,线程的用处大大降低。在 Python 中,真正的并行性是通过子进程实现的。