ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Monty 里的 asyncio 只有 run 和 gather?沙箱异步模型完整解析

2026/8/31 9:11:28 拓冰建站 浏览量
Monty 里的 asyncio 只有 run 和 gather?沙箱异步模型完整解析 Monty 里的 asyncio 只有 run 和 gather沙箱异步模型完整解析【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/montyMonty 是一个用 Rust 编写的安全 Python 沙箱解释器专门用来安全运行 AI 生成的代码。它把asyncio模块砍到了极限——只剩run和gather两个函数。这不是偷懒而是一次刻意的减法因为 Monty 沙箱里没有事件循环宿主host本身就是那个循环。想明白这一句你就理解了 Monty 整套异步模型的底层逻辑。 一句话记忆Monty 不管什么时候运行任务只管谁在等待什么。调度权完全交给宿主。为什么 Monty 的 asyncio 只剩 run 和 gather在完整 Python 里asyncio有一大堆东西sleep、create_task、wait、Queue、Lock、timeout……而 Monty 里这些统统不存在——你去取asyncio.sleep会直接报AttributeError。原因很简单一个事件循环要自己决定下一个跑谁它需要一个调度器。但 Monty 的设计哲学是沙箱不拥有任何自主权力。文件系统、网络、环境变量全部被屏蔽所有对外操作都要经过宿主放行。异步只是这套哲学的自然延伸沙箱不预占、不抢占、不起线程沙箱里没有调度器在空转谁先跑、何时恢复由宿主说了算所以能留在模块表面的只有两个纯数据 纯语义的函数asyncio.run(coro)把一个协程跑到底asyncio.gather(*awaitables)让多个任务并发收一批结果其余的都需要一个会自己转的循环才能成立于是被整个删掉。完整清单可在 limitations/asyncio.md 查阅。沙箱里到底有几种能等待的东西Monty 没有__await__协议可等待对象只有三类全是它内部认识的东西协程——async def调用后返回的协程对象gather 的未来对象——asyncio.gather(...)返回的结果外部函数调用未来——宿主绑定函数被调用时产生的ExternalFuture这三类正是 asyncio.rs 里定义的核心类型Coroutine、GatherFuture、ExternalFuture。理解它们就理解了沙箱异步的全部宇宙。asyncio.run 用法把协程跑到底asyncio.run(coro)只接受一个位置参数协程作用等价于在顶层await coro它会真正执行协程体直到结束协程return的值就作为结果返回协程里抛出的异常原样重新抛出它本身不干活只是把那个协程交给虚拟机去 await。实现见 modules/asyncio.rs 的run函数——它只做了一件事校验参数然后告诉虚拟机去 await 这个值。你可以把它理解成异步代码的入口点顶层没有await的语法位置时就用asyncio.run把协程启动起来。asyncio.gather 用法让任务真正并发asyncio.gather(*awaitables)是 Monty 异步并发的主力支持变长参数、*解包列表 / 元组都行把一批可等待对象打包并发执行最后按入参顺序返回一个结果列表。几个值得知道的细节顺序按入参不按完成时间——gather(slow(), fast())返回[slow, fast]慢的在前、快的在后不会乱序空 gather 返回空列表——await asyncio.gather()→[]重复传入同一协程只会跑一次——gather(c, c)只执行协程体一次却返回两份结果支持嵌套——gather(gather(...), gather(...))完全合法完成后可重复 await——同一个 gather 再次 await返回同一份缓存结果与 CPython 的 Future 行为一致这些行为都有测试用例覆盖例如 async__gather_all.py 就测了顺序、解包、去重、嵌套和重复 await。⚠️gather目前不接受任何关键字参数。你传return_exceptionsTrue会拿到NotImplementedError而不是 CPython 的TypeError。沙箱异步模型核心宿主驱动、协作式并发这是整篇文章最关键的认知。Monty 的并发是协作式 宿主驱动的。挂起 / 恢复宿主才是真正的循环当沙箱代码读到一个它自己没定义过的名字也就是要调用你宿主提供的函数执行会挂起。宿主拿到请求、跑完真实逻辑可能是发网络请求、查数据库把结果回填执行才恢复。解读一下解释器从不主动向外打。它只是停下等宿主告诉它发生了什么。这就是 docs/host-functions.md 反复强调的挂起—恢复模型。异步函数调用时宿主会返回一个ExternalFuture它有一个简单的状态机Pending等宿主解决→Resolved拿到了值或Failed宿主抛了错。等宿主把结果送回来被挂起的任务才会继续。任务Task是怎么被调度的底层调度在 scheduler.rs任务 0 主任务直接用虚拟机自己的栈和调用帧不额外存上下文任务 1 派生任务每个都把自己的栈、调用帧、异常状态序列化保存切换时再换回虚拟机每个任务处于四种状态之一Ready就绪、Blocked等某个可等待对象、Completed成功结束、Failed失败所谓并发其实是gather在它的每个分支都卡住等宿主结果时把整批挂起的请求一次性交给宿主等宿主把结果一起送回来再批量恢复。全程没有抢占、没有线程、没有沙箱内的调度器在空转。Monty 与 CPython 的 4 处关键差异避坑从 CPython 迁移到 Monty 时下面几处行为差异最容易踩坑全部记录在 limitations/asyncio.md#差异点CPythonMonty1gather 何时启动子任务创建gather就立刻把每个 awaitable 排成任务跑不跑都执行等到await那一刻才派生结果被丢弃的gather(...)一行代码都不跑2失败的 gather 留下的兄弟任务兄弟任务留在循环上继续转最终也会跑完兄弟任务只会在又有别的东西挂起时前进没人再 await 它就停在那儿3协程能否重复 await协程同样单次但语义更宽松协程是单次使用的重复 await 直接RuntimeError。需要复用请存结果别存协程4async for / async with支持解析期直接拒绝——没有异步迭代器、没有异步上下文管理器补充两条同样重要的await一个不可等待的东西会报TypeError 异步推导式[x async for x in ...]同样在解析期被拒 迁移心法别假设沙箱里有会自己转的循环。凡是依赖循环空转把孤儿任务喂完的写法在 Monty 里都要改成显式await驱动。在 Monty 里写异步代码的正确姿势结合 host-functions.md 的宿主异步函数一节几条实用建议用AsyncMonty接入异步宿主函数。external_lookup里的回调可以是协程函数宿主会帮你await。同步版Monty没法驱动协程宿主函数。把并发交给gather。想让多个宿主调用并发就用asyncio.gather(fetch(a), fetch(b))宿主会并发处理这一批。存结果别存协程。协程是单次的复用一律走缓存的返回值/异常。对外能力走external_lookup常量走inputs。前者是工具面按名懒加载后者是写死代码时依赖的少量值急切绑定。把宿主回调当不可信入参来校验。宿主函数在你自己的进程里、带着你自己进程的权限运行——沙箱拦得住代码碰不到你没给的东西拦不住你给的东西本身不安全。完整语义与边界建议对照 limitations/asyncio.md 与 docs/host-functions.md 一起读。一句话速查表你想知道的答案Monty 里 asyncio 有哪些函数只有run和gather沙箱里有事件循环吗没有宿主就是循环并发是怎么实现的协作式 宿主驱动gather挂起时把整批请求交给宿主可等待对象有几类协程、gather 结果、宿主外部 future共 3 类最大坑在哪gather 懒启动、协程单次、async for/with解析期被拒一句话总结Monty 的 asyncio 之所以只剩两个函数是因为它把何时执行的权力彻底交给了宿主只保留等待什么的语义。理解了宿主驱动、协作式并发这个核心Monty 的整个异步模型就再无秘密。【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考