在 async 视图里调用同步代码要用 sync_to_async。它有一个 thread_sensitive 参数,决定「所有同步代码是不是共用一个线程」。这篇在真实的 uvicorn 里量出两种模式的线程与耗时差别。
['ThreadPoolExecutor-2_0', 'ThreadPoolExecutor-2_0', 'ThreadPoolExecutor-2_0', 'ThreadPoolExecutor-2_0', 'ThreadPoolExecutor-2_0'] → 不同线程数 1
为什么要这样?因为 Django 的数据库连接是「每线程一个」,并且很多同步代码(ORM、部分中间件、事务)假定自己一直在同一个线程里。所以默认让同一个请求里的同步代码都回到同一个线程里执行。(这一段解释来自设计上的理解,本文实验验证的是「线程相同」这一现象。)
thread_sensitive=True 总耗时 2037 ms 用到的线程数 1 thread_sensitive=False 总耗时 206 ms 用到的线程数 10
同一请求里 gather 多个 sync_to_async,默认模式下会排队执行。要并发就得 thread_sensitive=False,但这时每个任务在不同线程里,ORM 会在每个线程里开自己的数据库连接(下一篇 115 会在 PostgreSQL 上量到)。
20 个并发请求总耗时 518 ms,用到 20 个不同线程
总耗时 819 ms,用到 14 个线程
50 个任务只用了 14 个线程,总耗时约 4 个批次。这个上限我没有去拆它是由什么决定的(默认线程池大小、ASGI_THREADS 等),只报告本机现象。