ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CPython 异步生成器并发安全修复:free-threading 下 asend/athrow 的原子状态机与单次使用语义

CPython 异步生成器并发安全修复:free-threading 下 asend/athrow 的原子状态机与单次使用语义 CPython 异步生成器并发安全修复free-threading 下 asend/athrow 的原子状态机与单次使用语义【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本篇文章基于 CPython 官方变更记录Misc/NEWS.d/next/Core_and_Builtins/2026-08-01-18-04-11.gh-issue-120321.k3XvQb.rst展开剖析 gh-issue-120321 所修复的两个核心问题在 free-threading无 GIL构建下异步生成器被多线程并发迭代/关闭/抛入时的线程安全问题以及athrow()返回的 awaitable 对象“单次使用”语义复用已完成的 awaitable 将抛出RuntimeError而非重新驱动生成器。读完本文你将理解 CPython 异步生成器底层 awaitable 的状态机设计、CAS 原子操作在Py_GIL_DISABLED构建下的作用并能对照源码与测试用例复现、验证这两项行为。变更记录原文与背景该 NEWS 条目位于 Misc/NEWS.d/next/Core_and_Builtins/全文如下Fix thread safety of asynchronous generators when iterated, closed or thrown into concurrently from multiple threads on the free threading build. Also make anathrow()awaitable single-use: reusing it after completion now raisesRuntimeErrorinstead of resuming the generator again.翻译并拆解为两个独立且相互关联的修复点free-threading 构建下的并发安全在Py_GIL_DISABLED无 GIL构建中同一异步生成器若被多个线程同时迭代__anext__/asend、关闭aclose或抛入athrow此前存在数据竞争可能导致未定义行为本次修复引入了基于原子 CAS 的状态机保证互斥与正确转移。athrow()awaitable 单次使用语义一个athrow()调用返回的 awaitable 对象在完成后无论正常结束还是因异常结束不可再次驱动再次向其send()/throw()将抛出RuntimeError(cannot reuse already awaited aclose()/athrow())而不是再次唤醒生成器继续执行。异步生成器控制协议的底层对象模型在 CPython 中异步生成器对象由PyAsyncGenObject表示其结构定义于 Include/internal/pycore_interpframe_structs.h。该结构通过_PyGenObject_HEAD(ag)宏展开出公共头段包含一组与本次修复直接相关的字段#define _PyGenObject_HEAD(prefix) \ PyObject_HEAD \ PyObject *prefix##_weakreflist; \ PyObject *prefix##_name; \ PyObject *prefix##_qualname; \ _PyErr_StackItem prefix##_exc_state; \ PyObject *prefix##_origin_or_finalizer; \ int8_t prefix##_hooks_inited; \ int8_t prefix##_closed; \ int8_t prefix##_running_async; \ int8_t prefix##_frame_state; \ _PyInterpreterFrame prefix##_iframe;其中ag_closed标记异步生成器是否已关闭ag_running_async标记生成器是否正被某个 awaitable 驱动执行ag_frame_state描述内部帧生命周期状态。后两者在本次并发修复中扮演关键角色。asend()、athrow()、aclose()三个方法各自返回一个独立的 awaitable 包装对象分别由PyAsyncGenASend与PyAsyncGenAThrow两个类型表示见 Objects/genobject.ctypedef struct PyAsyncGenASend { PyObject_HEAD PyAsyncGenObject *ags_gen; /* Can be NULL, when in the __anext__() mode (equivalent of asend(None)) */ PyObject *ags_sendval; int8_t ags_state; } PyAsyncGenASend; typedef struct PyAsyncGenAThrow { PyObject_HEAD PyAsyncGenObject *agt_gen; /* Can be NULL, when in the aclose() mode (equivalent of athrow(GeneratorExit)) */ PyObject *agt_typ; PyObject *agt_tb; PyObject *agt_val; int8_t agt_state; } PyAsyncGenAThrow;可见aclose()在实现层面等价于athrow(GeneratorExit)当agt_typ NULL时即处于 aclose 模式。每个 awaitable 都有一个int8_t状态字段ags_state/agt_state由以下枚举驱动typedef enum { AWAITABLE_STATE_INIT, /* new awaitable, has not yet been iterated */ AWAITABLE_STATE_ITER, /* being iterated */ AWAITABLE_STATE_CLOSED, /* closed */ } AwaitableState;这三态INIT → ITER → CLOSED构成了“awaitable 单次使用”语义的基础。并发安全的实现CAS 原子状态机两种并发形态与双层防护源码注释Objects/genobject.c明确指出了异步生成器被并发驱动的两种途径共享同一个asend()/athrow()awaitable由多个线程同时驱动多个不同的asend()/athrow()awaitable同时向同一个生成器发送数据。因此修复采用“双层 CAS”awaitable 自身的状态转移使用原子 CAS防止同一 awaitable 被并发驱动时发生状态错乱生成器级别的ag_running_async抢占使用原子 CAS防止不同 awaitable 同时驱动同一个生成器。在Py_GIL_DISABLED构建下原子原语由_Py_atomic_compare_exchange_int8提供#ifdef Py_GIL_DISABLED static bool async_gen_try_set_state(int8_t *state, int8_t *expected, int8_t new_state) { return _Py_atomic_compare_exchange_int8(state, expected, new_state); } # define _Py_ASYNC_GEN_TRY_SET_STATE(state, expected, new_state) \ async_gen_try_set_state((state), (expected), (new_state)) #else # define _Py_ASYNC_GEN_TRY_SET_STATE(state, expected, new_state) \ ((state) (new_state), true) #endif在传统 GIL 构建Py_GIL_DISABLED未定义下GIL 本身已保证互斥宏直接退化为普通赋值并恒返回true而在 free-threading 构建下则执行真正的原子比较交换确保多线程同时操作时只有一方能成功完成状态转移。生成器级抢占函数async_gen_try_claim_running遵循同样的策略在无 GIL 时对ag_running_async执行 0→1 的 CAS失败即视为“生成器已在运行中”。状态机的完整流转路径async_gen_athrow_sendObjects/genobject.c是理解本次修复的核心函数其执行路径大致如下读取当前状态以 relaxed 原子读加载agt_stateCLOSED 检测若状态已是AWAITABLE_STATE_CLOSED直接抛出RuntimeError(cannot reuse already awaited aclose()/athrow())——这正是“单次使用”的第一道闸门帧结束检测若生成器内部帧已结束FRAME_STATE_FINISHED则尝试将该 awaitable 置为 CLOSED并返回StopIteration状态推进在循环中反复执行_Py_ASYNC_GEN_TRY_SET_STATE把 INIT 推进为 ITERCAS 失败说明有并发竞争重试直至成功或发现已被置为 CLOSED生成器级抢占调用async_gen_try_claim_running尝试独占生成器失败则将该 awaitable 置为 CLOSED 并抛出RuntimeError(athrow(): asynchronous generator is already running)aclose 模式下对应消息为aclose(): asynchronous generator is already running执行驱动aclose 模式下调用_gen_throw(gen, 0, PyExc_GeneratorExit, ...)注入GeneratorExit否则调用_gen_throw(gen, 0, typ, val, tb)注入用户异常并把产出值经async_gen_unwrap_value解包收尾置位无论成功产出、抛出StopAsyncIteration/GeneratorExit还是出现其他错误最终都会将agt_state原子置为 CLOSED并把ag_running_async释放回 0保证该 awaitable 此后不可复用且生成器可被下一个 awaitable 继续驱动。旧行为为何危险在本次修复之前一个需要多步send()才能完成的athrow()awaitable在完成后仍处于可驱动状态再次send()会重新唤醒生成器继续执行——即“恢复生成器”的旧语义。这既违反 awaitable 的生命周期约定也会在并发场景下造成不可预期的状态错乱。修复后所有结束路径yield 完成、yield_close、check_error都无条件执行状态收尾从根上消除了“死而复生”的可能。测试验证回归用例如何覆盖修复点测试文件 Lib/test/test_asyncgen.py 中包含大量直接针对 gh-issue-120321 的回归测试。并发驱动抛错test_async_gen_athrow_throw_concurrent_with_sendLib/test/test_asyncgen.py验证当生成器正被某个asend()驱动时另一线程/协程通过athrow(MyExc)抛入会得到RuntimeError: athrow(): asynchronous generator is already running且该athrow()awaitable 随即进入 CLOSED 状态再次send(None)会得到RuntimeError: cannot reuse already awaited aclose()/athrow()test_async_gen_athrow_throw_concurrent_with_throwLib/test/test_asyncgen.py则覆盖“两个 athrow 并发抛入”的变体验证ag_running_async抢占逻辑对同类操作同样生效。单次使用语义test_async_gen_send_same_athrow_coro_after_completionLib/test/test_asyncgen.py是本次修复最直接的回归用例其注释明确标注gh-120321an athrow() awaitable that needs more than one send() to complete must be closed on completion; sending to it again must raise instead of resuming the generator.用例构造了一个需要两次send()才能完成的场景生成器捕获ValueError后await一个YieldOnce对象挂起第二次send(None)才产出2并结束随后第三次send(None)必须抛出RuntimeError而非再次恢复生成器。同文件中的test_async_gen_await_same_aclose_coro_twiceLib/test/test_asyncgen.py、test_async_gen_throw_same_aclose_coro_twice等用例进一步确认 aclose 路径即athrow(GeneratorExit)模式同样遵循单次使用约定。兼容性回归测试同时保留了原有行为的回归保护test_async_gen_aclose_twice_with_different_corosLib/test/test_asyncgen.py确认使用不同的aclose()awaitable 对象对同一生成器调用两次aclose()依然合法——单次使用约束针对的是单个 awaitable 对象而非生成器本身。对开发者与用户的实践影响行为变化复用已完成的athrow()/aclose()awaitable 对象从“静默恢复生成器”变为显式抛出RuntimeError(cannot reuse already awaited aclose()/athrow())。所有严格遵循await语义每个 awaitable 只 await 一次的既有代码不受影响。错误信息语义cannot reuse already awaited ...与asynchronous generator is already running是两条不同的错误前者针对同一个 awaitable 对象的二次驱动状态机已 CLOSED后者针对生成器级的并发冲突ag_running_async抢占失败。free-threading 构建该修复仅在Py_GIL_DISABLED构建下启用真正的原子 CAS 路径传统 GIL 构建依赖 GIL 保证互斥行为保持兼容但同样享受单次使用语义的修复。并发编程约束即便修复后也不应刻意设计多线程共享驱动同一异步生成器的代码修复保证的是“错误可检测、行为确定”而非鼓励无锁并发驱动。正确的做法仍是为生成器加锁或使用单消费者模型。总结gh-issue-120321 的修复为 CPython 异步生成器补齐了 free-threading 构建下的并发安全短板并收紧了athrow()/aclose()awaitable 的生命周期语义。其核心是一套以AwaitableState枚举为骨架、以原子 CAS 为手段的双层状态机agt_state/ags_state管住单个 awaitable 的单次使用ag_running_async管住生成器级互斥。相关实现与测试均可在 Objects/genobject.c 与 Lib/test/test_asyncgen.py 中直接查阅与复现是理解 CPython 无 GIL 并发模型下对象生命周期管理的绝佳范例。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表