异步编程函数用法 - starlette.concurrency里的run_in_threadpool


前言

看langchain-chatchat代码的时候看到这一段,本来打算一眼过,忽然发现这个名字上看起来是线程池的地方居然可以用 await。可以把协程的编程方法用在这里,很神奇。
看了下引入的包 from fastapi.concurrency import run_in_threadpool,追踪进去 实际上是这样
from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa
那么我们就学习一下这个 run_in_threadpool 的用法
在这里插入图片描述


一、run_in_threadpool是什么?

在FastAPI 库中引入了 Starlette 的一些功能和组件,其中包括用于在异步环境中运行同步代码的 run_in_threadpool 函数。Starlette 是一个轻量级的 ASGI 框架,提供了构建高性能异步 Web 应用程序的核心组件,而 FastAPI 则是在 Starlette 的基础上构建的,提供了更高级的功能和简化的接口。

二. run_in_threadpool 的工作原理

在 Starlette(以及被 FastAPI 使用的版本)中,run_in_threadpool 函数是通过将任务提交给 concurrent.futures.ThreadPoolExecutor 实例来实现的。这种方法允许我们在异步代码中运行阻塞的同步代码。

让我们逐步解析 run_in_threadpool 的实现及其工作原理。

1. 创建或获取事件循环

首先,需要获取当前的事件循环,通常在协程中可以通过 asyncio.get_event_loop() 获取:

import asyncio

loop = asyncio.get_event_loop()

2. 创建线程池

线程池由 concurrent.futures.ThreadPoolExecutor 提供。在实际实现中,线程池是一个全局单例,这样可以避免频繁创建和销毁线程池,提高性能。

from concurrent.futures import ThreadPoolExecutor

# 创建全局的线程池实例
executor = ThreadPoolExecutor()

3. 提交任务到线程池并等待结果

使用事件循环的 run_in_executor 方法,将任务提交到线程池执行。run_in_executor 会返回一个 Future 对象,表示异步执行的结果。

async def run_in_threadpool(func, *args, **kwargs):
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(executor, func, *args, **kwargs)

4. 完整示例

综合以上步骤,我们来看一个完整的 run_in_threadpool 实现:

import asyncio
from concurrent.futures import ThreadPoolExecutor

# 创建全局的线程池实例
executor = ThreadPoolExecutor()

async def run_in_threadpool(func, *args, **kwargs):
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(executor, func, *args, **kwargs)

三. 如何在 FastAPI 中使用

我们来看一个 FastAPI 应用示例,展示如何使用 run_in_threadpool 运行阻塞的同步代码:

from fastapi import FastAPI
from fastapi.concurrency import run_in_threadpool
import time

app = FastAPI()

# 定义一个阻塞的同步函数
def blocking_task():
    time.sleep(5)
    return "Task Complete"

# 定义一个异步路由处理函数
@app.get("/run-blocking/")
async def run_blocking():
    result = await run_in_threadpool(blocking_task)
    return {"result": result}

四、使用它的好处

run_in_threadpool 实际上融合了 Python 的多线程和协程两种并发模型,使得它们可以在同一个应用程序中协同工作。这种融合允许你在异步编程环境中执行同步代码,而不会阻塞事件循环。

run_in_threadpool 和 Python 自带的多线程(如 threading 模块)相比,具有一些显著的优势,特别是在异步编程的上下文中。以下是两者之间的一些主要区别和 run_in_threadpool 的优势:

1. 简化异步编程

run_in_threadpool 的主要优势在于它与异步编程模型的无缝集成。使用 run_in_threadpool,可以在异步函数中直接调用同步函数,并使用 await 等待其完成。这使得代码更加简洁和易读。

from fastapi.concurrency import run_in_threadpool

async def some_async_function():
    result = await run_in_threadpool(some_sync_function)
    # 继续处理 result

相比之下,使用 threading 需要在异步环境中管理线程的生命周期,并且不能直接使用 await 等待结果,可能需要结合 concurrent.futures.Future 或者其他回调机制,代码会更加复杂。

2. 内置的线程池管理

run_in_threadpool 利用 FastAPI 内置的线程池管理。这意味着你不需要手动创建和管理线程池,减少了管理线程池的复杂性和潜在的资源泄露风险。

import threading

def some_function():
    pass

# 使用 threading 手动管理线程
thread = threading.Thread(target=some_function)
thread.start()
thread.join()

在这种情况下,你需要手动管理线程的创建和销毁。而使用 run_in_threadpool,这些都被框架自动处理了。

3. 更好的与异步框架集成

在使用像 FastAPI 这样的异步框架时,run_in_threadpool 提供了更好的集成。这意味着在处理 HTTP 请求时,可以更高效地调度阻塞的同步任务而不会阻塞事件循环,从而提高并发性能。

from fastapi import FastAPI
from fastapi.concurrency import run_in_threadpool

app = FastAPI()

def blocking_task():
    # 一些阻塞操作
    pass

@app.get("/")
async def read_root():
    result = await run_in_threadpool(blocking_task)
    return {"result": result}

如果使用 threading,代码将会变得更加复杂,不容易维护。

4. 自动处理异常

run_in_threadpool 可以更方便地捕获和处理在线程池中执行的函数抛出的异常,而不需要额外的处理逻辑。这使得代码更加健壮。

from fastapi import FastAPI, HTTPException
from fastapi.concurrency import run_in_threadpool

app = FastAPI()

def blocking_task():
    raise Exception("Some error")

@app.get("/")
async def read_root():
    try:
        result = await run_in_threadpool(blocking_task)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))
    return {"result": result}

如果使用 threading,你需要自己实现异常捕获和处理机制。

总结

run_in_threadpool 实现了多线程和协程的融合,通过将阻塞的同步任务委托给线程池执行,并使用 await 等待任务完成,使得在异步编程环境中能够有效处理同步操作。这样既能利用线程池的并发能力,又能保持事件循环的高效运行,从而提高整体应用程序的性能和响应能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值