异步并发怎么做?

1、flask的异步并发

问题

  • flask在开发环境下是单线程的,如果某个请求长时间无响应(阻塞),会导致其他请求也无法响应。
  • flask原生并不提供异步并发能力。也就是说,如果使用flask开发的app接口,即使该接口使用了协程异步编程,但在外部并发请求时,flask还是会把这些 "已经异步编程的接口"当成 同步接口执行。

解决办法

  • 1、使用多线程或者多进程的服务器,例如使用 Gunicorn 或者 uWSGI 部署 Flask 或 FastAPI 应用。这些服务器可以同时处理多个请求。
  • 2、使用异步非阻塞的服务器,例如 Tornado 或者 Twisted。这些服务器可以在处理一个请求的时候,如果遇到 IO 阻塞,就先去处理其他的请求,等 IO 完成后再回来继续处理这个请求。
  • 3、使用异步编程,例如 asyncio 或者 gevent。这些库可以让你的代码在遇到 IO 阻塞的时候,自动切换到其他的任务,从而提高整体的并发性能。
    但flask并不真正提供异步运行的能力,推荐支持异步编程的 Web 框架,如fastapifastapi官方文档】、aiohttpaiohttp官方文档】。
    备注: aiohttp既可以做客服端(发起请求),又可以做服务端(接受并处理请求),支持http协议,因此可以做web框架。 但aiohttp比较底层,写起来代码较多。
    客户端-浅度测评:requests、aiohttp、httpx 我应该用哪一个?
    【异步并发编程】使用aiohttp构建Web应用程序
  • 4、对于超时的请求,可以考虑设置一些超时机制,例如使用 future 或者 promise,如果一个请求超时,就直接返回错误,不再等待它完成。这样可以避免一个请求阻塞住整个服务器。

实现方案

(1)flask + 异步视图装饰器

本质: 多线程并发。虽然宏观上看好像是异步并发(单线程的并发)。

虽然asynic await关键字旨在实现同一线程里的并发,但flask并不原生支持这一能力(“不支持”不是说“不能使用这些关键字”)。
所以,同一时间、同一线程、多个请求并发请求时,不管有无asynic await关键字修饰请求接口,flask都是一个一个顺序的、同步处理他们,而不是并发处理。

这里的装饰器实现的功能:
Making Flask async and Quart sync, Quart 的作者 PG Jones 给出了一个 Flask 异步化的代码,route 方法可加上 async 关键字@run_async 装饰

当并发请求时,给每个请求开创一个线程单独的处理,这样并发的请求就被放到了多线程里,从宏观上看就是并发了,这样也不会因为某个接口请求阻塞而导致其他接口也无法响应的问题。
但这种异步装饰器,只是宏观上的“像”协程并发(协程:单线程下的并发),其实是多线程并发

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project :hippo-ai-py 
@Author  :cf
@Date    :2024/4/2
@Desc    : api公用信息
'''

import asyncio
from concurrent.futures import Future, ThreadPoolExecutor
from functools import wraps

from flask import Flask, has_request_context, copy_current_request_context


def run_async(func):
    '''
    flask异步视图装饰器。
    Args:
        func: 调用的方法

    Returns:

    '''
    @wraps(func)
    def _wrapper(*args, **kwargs):
        call_result = Future()

        def _run():
            loop = asyncio.new_event_loop()
            try:
                result = loop.run_until_complete(func(*args, **kwargs))
            except Exception as error:
                call_result.set_exception(error)
            else:
                call_result.set_result(result)
            finally:
                loop.close()

        loop_executor = ThreadPoolExecutor(max_workers=1)
        if has_request_context():
            _run = copy_current_request_context(_run)
        loop_future = loop_executor.submit(_run)
        loop_future.result()
        return call_result.result()

    return _wrapper


app = Flask(__name__)

async def fetch(url):
    print(f"{threading.current_thread().name}:{url}")
    return requests.get(url).text


async def main(t):
    await asyncio.sleep(t)
    tasks = [fetch(url) for url in ["https://baidu.com", "https://bing.com", "https://yanbin.blog"]]
    return await asyncio.gather(*tasks)


@app.route("/")
@run_async
async def index():
    time1 = time.time()
    responses = await main(3)
    time2 = time.time()
    print(f'{threading.current_thread().name}--3:response sizes: {[len(res) for res in responses]},耗时{time2 - time1}s\n')
   return responses 


if __name__ == "__main__":
    # app.run(debug=False, use_reloader=False,threaded=False)  
    app.run(debug=False, use_reloader=False)
(2)WSGI启动服务

本质: 多线程、多进程的并发。

如何在 uwsgi 配置中传递自定义参数?

2、fastapi异步编程

本质: 协程(单线程)并发。

待续。。。。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值