为何这段Flask代码表现出异步运行特性?IO绑定请求疑问
我原本计划在Flask中使用asyncio与aiomysql,因此开展了一项简单实验,但遇到了认知矛盾点。我一直认为Flask以单线程顺序处理请求,当处理IO绑定请求时,无法同时处理其他请求,但实际结果与该认知不符,特此请教其中原因。
实验代码
from flask import Flask, Response, jsonify import asyncio import aiomysql import pymysql app = Flask(__name__) # 注:代码中未展示conn的初始化逻辑,推测是提前创建了pymysql连接 @app.route('/sync2') def hihi2(): print('IO bound start!') cur = conn.cursor() cur.execute('''select Count(*) from fruit_defect WHERE date between '2022-11-22' and '2022-11-25' ''') print('IO Bound finish!') result = cur.fetchall() print(result) cur.close() return jsonify(result) @app.route('/sync3') def hihi3(): print('CPU BOUND start!') c = 0 for _ in range(1000000): c += 1 print('CPU bound finish!') return jsonify(1)
实验结果
同时发起对/sync2(IO绑定请求)和/sync3(CPU绑定请求)的请求后,控制台输出顺序如下:
IO bound start!CPU BOUND start!CPU bound finish!IO Bound finish!
可见/sync3的CPU密集任务在/sync2的IO等待期间完成了,说明两个请求被同时处理。
原因分析
Flask默认开发服务器的多线程配置:
Flask默认使用的Werkzeug开发服务器,在调试模式(debug=True)下默认开启多线程(threaded=True)。这意味着服务器会创建多个线程来处理并发请求,当某个线程在等待IO(比如数据库查询)时,其他线程可以继续处理新的请求,所以你看到了并发处理的现象。单线程模式的验证方式:
如果你想验证Flask单线程下的阻塞行为,需要在启动时显式关闭多线程:if __name__ == '__main__': app.run(threaded=False)此时再测试,
/sync2的IO请求会阻塞整个服务器,/sync3必须等/sync2完成后才能开始处理。同步数据库连接的影响:
你的代码中使用的是同步的pymysql而非异步的aiomysql,所以/sync2是同步IO操作,但因为服务器是多线程,所以其他线程可以并行处理/sync3的请求,这进一步强化了并发的表现。
总结
你的核心认知(单线程Flask会在IO绑定请求时阻塞其他请求)是正确的,但忽略了Flask默认开发服务器在调试模式下的多线程默认配置,导致实验结果与预期不符。若要测试单线程下的阻塞逻辑,需显式设置threaded=False。
内容的提问来源于stack exchange,提问作者KIM Bucket

