Python中如何使用Flask + Requests + 多线程?

最近在做一个 Demo:
[硬件 GPS 模块] 采集 GPS 数据发送到 [物联网服务器] , [我的网站] 定时 10 秒向 [物联网服务器] 请求 GPS 数据更新信息。

现在遇到 requests 请求 gps 数据时候阻塞,导致网站卡顿,故想到使用 Python 的多线程解决:
线程 A: 主线程,跑 flask
线程 B:Requests 线程,睡眠 10 秒,唤醒请求 GPS 数据

不考虑使用前端请求,就讨论用 Python 线程解决,如果是嵌入式的话可以这样做:
1. 在线程 A 中创建低优先级的线程 B
1. 有网络请求时执行线程 A,空闲时执行线程 B,这样既不影响用户网页请求,又可以发起 Requests 请求更新 GPS 数据

回到 Flask 和 Python,查了一下 Flask 也有多线程模式,我尝试开启 threaded 选项,并未解决阻塞问题。

查了 Python 3.7 的新特性,asyncio,能否使用 asyncio 解决多线程问题呢?

对 Python 的理解很粗浅,顺便上两个没太理解的链接:
https://hackernoon.com/how-to-run-asynchronous-web-requests-in-parallel-with-python-3-5-without-aiohttp-264dc0f8546

https://medium.com/velotio-perspectives/an-introduction-to-asynchronous-programming-in-python-af0189a88bbb
Python中如何使用Flask + Requests + 多线程?


16 回复

用 celery 啊


在Flask应用里用Requests发外部请求,用多线程避免阻塞,可以这样搞:

from flask import Flask, jsonify
import requests
import concurrent.futures
import time

app = Flask(__name__)

def fetch_url(url):
    """线程任务:获取URL内容"""
    try:
        response = requests.get(url, timeout=5)
        return {'url': url, 'status': response.status_code, 'content': response.text[:100]}
    except Exception as e:
        return {'url': url, 'error': str(e)}

@app.route('/fetch-multi')
def fetch_multiple():
    urls = [
        'https://httpbin.org/delay/2',
        'https://httpbin.org/delay/3',
        'https://httpbin.org/json'
    ]
    
    results = []
    # 用ThreadPoolExecutor创建线程池
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        # 提交所有任务
        future_to_url = {executor.submit(fetch_url, url): url for url in urls}
        
        # 收集结果
        for future in concurrent.futures.as_completed(future_to_url):
            results.append(future.result())
    
    return jsonify(results)

if __name__ == '__main__':
    app.run(debug=True)

这里的关键点:

  1. ThreadPoolExecutor 管理线程池,比手动创建线程更安全
  2. as_completed 按完成顺序获取结果,不用等最慢的那个
  3. 每个线程独立执行 requests.get(),互不干扰
  4. Flask路由函数保持非阻塞,快速响应

访问 /fetch-multi 会并发请求三个URL,总耗时约3秒(最慢的那个),而不是串行的7秒。

注意线程数别设太大,requests库本身有连接池限制。如果要做更复杂的并发控制,可以考虑用 asyncio + aiohttp,但线程池方案对大多数场景够用了。

用线程池处理IO密集型任务就行。

会不会是 flask 的多线程模式跟你要的多线程模式有区别?

几种解决方法:
1. 多线程,不是让你开启 flask 的多线程模式,而是用 threading 模块再开启一个线程跑你的任务
2. flask 换成 starlette,requests 换成 requests-async,全部写异步代码
3. celery

  1. 多进程,不是 CPU-bound 任务的话没必要,多线程就可以解决了

你这个阻塞是网络问题,不是机器性能问题。就算用 thread 消耗也不大,先优化下网络请求,不行再优化性能方面

不如 mqtt

你解决的问题的思路没对。
1. 更新 GPS 数据应该采用异步定时任务实现,可用 celery 或者 dramatiq+apscheduler 实现。
2. flask 部署采用 gunicorn+gevent 实现就行了。

谢谢大家!



试了一下 celery,有点云里雾里的,只是想用多线程解决 Requests 阻塞问题,为什么 celery 要用到 redis,我再了解看看。

楼主呀,gevent 就是最好的解决方案。 不用上 celery。。

读起来真有点头疼,我翻来覆去看了三四遍也没看太懂。

『现在遇到 requests 请求 gps 数据时候阻塞,导致网站卡顿,故想到使用 Python 的多线程解决:』

1. requests 请求数据为什么会有阻塞?调物联网服务器接口不是调个 API 吗?几百 ms 吧?
2. 网站为什么有卡顿?用户每次看网页都要从物联网服务器取数据呀?为什么不把数据准备好呢?
3. 架构不明,不知道为什么使用多线程

您可以使用 ProcessOn 绘制图片

感谢两次推荐,我去了解一下 gevent 用法。

抱歉,背景可能没有交代清楚

> 1. requests 请求数据为什么会有阻塞?调物联网服务器接口不是调个 API 吗?几百 ms 吧?
这只是个 Demo,我的 Flask 网站调用物联网服务器接口,物联网服务器向 IOT 设备请求数据,IOT 设备返回给物联网服务器,物联网服务器再返回我的 Flask 网站,中间可能耗时 3、4 秒。简单来说姑且认为是一个 Requests 请求会耗时 3、4 秒。实际上即便几百 ms 也是不可接受的,requests 是阻塞请求,在此期间用户向我的网站请求网页无法得到及时响应。

> 2. 网站为什么有卡顿?用户每次看网页都要从物联网服务器取数据呀?为什么不把数据准备好呢?
可以准备好,例如在最新数据更新前先返回历史数据。但总会遇到 Requests 阻塞场景,想知道大家怎么解决。

> 3. 架构不明,不知道为什么使用多线程
简单来说,就做两件事情,
1. 运行 Flask,响应网页请求
2. 间隔 10 秒像第三方物联网服务器发起 Requests 请求

矛盾冲突:
由于 Requests 请求耗时较长,导致 Flask 程序无法及时响应网页请求。

> 为什么使用多线程?
可能用了错误的思维去解决这个问题,因为在嵌入式中,同优先级两个任务会共享 CPU 时间,时间片轮转,这样就能并行处理上述的两件事情。

#13

1. 为什么有阻塞
噢!我现在明白多了。我想这是架构的问题——不知道架构这个取词是否洽当——,要是我来开发不会让数据每次都要这么走一遍的,各个节点都会有缓存 /数据库。要是换成 NBIOT 那种网络,你可能就不会让数据这么走了,嘻嘻。

2. 可是使用缓存,但想问问大家怎么最优地解决 Requests 阻塞
『总会遇到 Requests 阻塞场景』,在 Web 服务器(也就是 Flask 吧?)每秒更新一次数据,用的时候直接用 Web 服务器上积累下来的数据,这和普通的页面速度就一样了吧?

我的意思可能是……解耦?

3. 现在是什么架构?
我想前两问可以体现我的思想了,再多的俺也不会了

再次感谢,gevent 是正确的解决方案。

总结一下这个问题,用 threading 是不对的,Python 中的 threading 没有优先级区分,也就是说执行到 B 线程的 requests 耗时请求,CPU 还是会死等。同时 Python 的 threading 不能被销毁、停止、暂停、恢复或中断。

什么情况下用 threading?
当你每个 thread 中的任务的每行代码都是在干实事,没有等待、睡眠等无意义操作时可以用 threading。

目前还在看资料,想搞懂有了 async 后是否还有使用 celery 和 gevent 的必要以及它们的区别。

参考资料:
https://docs.python.org/zh-cn/3.6/library/threading.html

回到顶部