bigywater.tech
Flask · 143

Thread Local 与请求隔离

request 为什么能在任何地方直接用?它是怎么做到每个请求互不干扰的?新线程里为什么读不到?全局变量安全吗?这篇用真实的多线程把每一层都验证一遍。

ContextVar
Werkzeug Local 的存储,不是 threading.local
29 / 100
100 次并发读算写,不加锁时全局计数的结果
copy_current_request_context
把请求上下文带进新线程的办法

LocalProxy 与 ContextVar

实测 · 本机执行输出
  type(request) = LocalProxy | 真实对象 = Request
  type(g) = LocalProxy | 真实对象 = _AppCtxGlobals
  type(current_app) = LocalProxy | 真实对象 = Flask
  Werkzeug Local 的存储: Local 的源码里用到了 ContextVar: True | 用到了 threading.local: False

上一篇:Greenlet 与 Flask Context;下一篇:Gunicorn Worker 类型。

线程各有各的上下文

实测 · 本机执行输出
  三个线程各自读到: {'A': '/a', 'B': '/b', 'C': '/c'}
  上下文外访问 request.path → Working outside of request context.

后台线程里的 request

实测 · 本机执行输出
  新线程里直接读 request.path → RuntimeError: Working outside of request context.
  用 copy_current_request_context 包装后 → /parent

g 的归属

实测 · 本机执行输出
  内层 g.v = inner
  回到外层 g.v = outer
  请求上下文里的 g 就是它所属应用上下文的 g: True 1

全局变量不隔离

实测 · 本机执行输出
  100 次并发 POST:不加锁的计数 = 29(期望 100),加锁的计数 = 100

分步演示

Thread Local 与请求隔离
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。gunicorn gthread worker
    • 没有验证:Werkzeug 旧版本(2.0 之前基于 threading.local 与 greenlet 的实现)的差别、asyncio 任务里上下文的复制时机、current_app 在应用工厂与测试里的推入顺序、全局变量在多进程部署下的表现(见第 145 篇)
    京ICP备2026067502号-1京公网安备11010502063487号