bigywater.tech
Flask · 170

压测下的全局状态问题

压测能不能发现全局状态的并发问题?什么样的竞争压测碰得到、碰不到?为什么「压测没出错」不等于线程安全?这篇用 ab 与真实的 gunicorn 线程,把三种全局状态问题各压一遍。

0 / 43% / 79%
读写间没有窗口、0.0001 秒窗口、0.001 秒窗口时压测里丢失的更新
24 / 3000
遍历全局字典同时被修改时返回 500 的请求数(并发 32)
没出错 ≠ 安全
共用 sqlite3 连接的压测没发现问题,但不能据此认为线程安全

计数器的窗口

实测 · 本机执行输出
  窗口 0(中间没有任何让出)           计数 2000 / 2000(丢了 0 次,0.0%)
  窗口 0.0001 秒              计数 1134 / 2000(丢了 866 次,43.3%)
  窗口 0.001 秒               计数 422 / 2000(丢了 1578 次,78.9%)
  加锁(窗口 0.001 秒的写法)  计数 2000 / 2000

上一篇:大文件响应性能;下一篇:Flask Development Server 与生产服务器(第 171 篇起是部署部分)。

第 143、145 篇从线程与进程的角度讲过全局变量,这一篇用压测的角度看它。第一次运行时我用 time.sleep(0) 当「读写之间的窗口」,一点竞争也没压出来,才发现窗口大小这个变量,于是加了几档窗口。

遍历时被修改的全局字典

实测 · 本机执行输出
  /sum 并发  1(同时有 /mutate 在改字典):3000 个请求里非 2xx 50 个;日志里「dictionary changed size during iteration」50 次
  /sum 并发 32(同时有 /mutate 在改字典):3000 个请求里非 2xx 24 个;日志里「dictionary changed size during iteration」24 次

共用的数据库连接

实测 · 本机执行输出
  共用一个连接         3000 个请求(32 并发):非 2xx 0 个 | 日志里的异常类型 []
  每个请求自己的连接      3000 个请求(32 并发):非 2xx 0 个 | 日志里的异常类型 []

分步演示

压测下的全局状态问题
本步的对象

    本文用到的工具与边界

    • 本机 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、ApacheBench
    • 没有验证:多进程下全局变量的不一致(第 145 篇)、其它数据库驱动共用连接的行为、requests.Session 等共享客户端对象的线程安全、asyncio 下的类似问题、线程安全检测工具(如 ThreadSanitizer 的 Python 支持)
    京ICP备2026067502号-1京公网安备11010502063487号