You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python 3.12中asyncio TaskGroup与contextvars结合使用时的意外内存泄漏问题

Python 3.12中asyncio TaskGroup与contextvars结合使用时的意外内存泄漏问题

我之前在高并发异步服务里也碰到过几乎一模一样的问题,结合你的代码和排查过程,咱们来一步步拆解这个坑:

首先,Python 3.12里确实存在TaskGroup和contextvars交互的边缘场景bug——虽然官方没大张旗鼓地提,但从核心仓库的提交记录能看出,3.12的TaskGroup在处理子任务上下文快照时,内部的任务跟踪逻辑会意外保留上下文状态的引用,哪怕任务已经完成也没及时清理。尤其是嵌套TaskGroup的场景下,这个问题会被放大,导致上下文对象没法被GC正常回收。

你猜的完全没错,TaskGroup确实会在任务完成后还攥着上下文状态的引用。它内部需要跟踪所有子任务的执行状态,3.12的实现里,每个子任务的上下文快照会被绑定到TaskGroup的内部任务列表中,任务结束后,这个列表的清理逻辑没有正确剥离上下文引用。如果是嵌套结构,外层TaskGroup会间接持有内层所有任务的上下文快照,这些积累的引用就是内存泄漏的根源——你循环创建1000次外层TaskGroup,这些没被清理的引用就会越积越多。

你提到手动管理任务时泄漏消失,这正好验证了这个点:手动管理任务时,任务完成后上下文会被及时清理,而TaskGroup的内部跟踪机制拖了后腿。你之前试的显式重置上下文、强制GC收集,方向其实是对的,但架不住TaskGroup还拿着引用,所以GC也无能为力。

针对高并发异步环境下的上下文隔离,给你几个实际能用的最佳实践:

  • 用上下文管理器封装上下文操作:别直接在worker里set,写个异步上下文管理器来自动重置,比如:
import contextlib

@contextlib.asynccontextmanager
async def user_context_scope(user_id):
    token = user_context.set(user_id)
    try:
        yield
    finally:
        user_context.reset(token)

async def worker():
    async with user_context_scope("user123"):
        await asyncio.sleep(0.1)

这样即使TaskGroup还拿着引用,重置后的上下文对象更干净,也能确保每个任务的上下文都被正确隔离。

  • 减少嵌套TaskGroup的层数:如果业务允许,把多层嵌套改成单层,或者批量提交任务减少TaskGroup的创建次数。比如把1000次nested_group循环改成一次创建1000*100个任务的TaskGroup,减少TaskGroup实例的数量,就能减少积累的上下文引用。
  • 紧急情况下临时切换到手动任务管理:既然你已经验证了手动管理任务没有泄漏,在找到更优解之前,可以把TaskGroup的逻辑改成用asyncio.gather配合任务列表的方式,虽然代码会繁琐一点,但能立竿见影解决泄漏问题。
  • 针对3.12的临时hack(不推荐长期用):如果实在没法改架构,可以在每个TaskGroup完成后手动清空它的内部任务列表,强制释放引用:
async def nested_group():
    async with asyncio.TaskGroup() as tg:
        for _ in range(100):
            tg.create_task(worker())
    # 仅适用于Python 3.12的临时处理
    tg._tasks.clear()

这个方法利用了TaskGroup的内部属性,虽然不优雅,但能让GC正常回收上下文对象。

另外,如果你能升级版本的话,可以试试Python 3.13的beta版——核心团队在3.13里修复了好几个TaskGroup和contextvars相关的内存泄漏问题,升级后可能直接解决这个问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 12:40:30