ARTICLE DETAIL

资讯详情

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

gevent.local 深入解析:Greenlet 局部对象(Greenlet-local Data)的原理与实战

gevent.local 深入解析:Greenlet 局部对象(Greenlet-local Data)的原理与实战 后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载导读本文围绕 gevent 的gevent.local模块展开它是 gevent 中实现“数据按绿色线程greenlet隔离”的核心机制等价于标准库threading.local在绿色线程世界的对应物。读完本文你将掌握gevent.local.local的基本用法、跨 greenlet 的数据隔离行为、如何通过子类化实现带默认值与初始化逻辑的局部对象、__slots__的共享语义以及其底层基于弱引用与描述符的源码实现并了解它在gevent.thread、gevent.threading、gevent.contextvars与 monkey-patch 中的实际应用。一、什么是 Greenlet-local 对象gevent.local模块API 文档见 docs/api/gevent.local.rst实现见 src/gevent/local.py提供了一种名为local的对象类型其语义与标准库threading.local一一对应只是把“线程”换成了“greenlet”。它的设计初衷来自 Python 标准库_threading_local.py模块文档字符串中明确说明本实现基于 Python 3.4 的_threading_local.py。如果你有希望属于某个 greenlet 私有的数据只需创建一个 greenlet-local 对象并把数据挂到它的属性上 import gevent from gevent.local import local mydata local() mydata.number 42 mydata.number 42你也可以直接操作 local 对象的字典__dict__ mydata.__dict__ {number: 42} mydata.__dict__.setdefault(widgets, []) [] mydata.widgets []注意__dict__在 gevent.local 中是可读但只读的__setattr__与__delattr__中对__dict__会直接抛出AttributeError参见 src/gevent/local.py 的实现测试用例test_cannot_set_delete_dict也验证了这一点见 src/gevent/tests/test__local.py。模块通过__all__ [local]对外只暴露local这一个公开符号。二、核心语义数据按 greenlet 隔离greenlet-local 对象最关键的特性是其数据是 greenlet 局部的。在一个 greenlet 里写入的值在另一个 greenlet 里读不到不同 greenlet 各自拥有独立的属性字典。 log [] def f(): ... items list(mydata.__dict__.items()) ... items.sort() ... log.append(items) ... mydata.number 11 ... log.append(mydata.number) greenlet gevent.spawn(f) greenlet.join() log [[], 11]可以看到在新 greenlet 中mydata.__dict__一开始是空的[]首次访问时才会创建该 greenlet 自己的字典写入number 11后也只影响那个 greenlet 的视图。回到原来的 greenlet mydata.number 42原来的值依然是 42丝毫不受其他 greenlet 修改的影响。这里有一个值得牢记的注意事项你从 local 对象取出的任何值包括__dict__本身都只对取值那一刻“当前”的 greenlet 有效。因此一般不建议把这些值跨 greenlet 保存因为它们只适用于它们所来自的那个 greenlet。隔离是如何实现的源码视角从 src/gevent/local.py 的注释可以清晰看到整体数据结构设计# 对于每个 local() 对象 # greenlet.__dict__[key_prefix str(id(local))] # _localimpl.dicts[id(greenlet)] (ref(greenlet), {})也就是说每个local()对象在创建时__cinit__会构造一个_localimpl实例其中维护一个dicts字典dicts以id(greenlet)为键值为_localimpl_dict_entry包含对 greenlet 的弱引用wrgreenlet和该 greenlet 专属的属性字典localdict同时在当前 greenlet 自身的__dict__中用_gevent_local_localimpl_ str(id(local))这样的前缀键见key_prefix保存对_localimpl的弱引用把两边关联起来。当属性被读写时__getattribute__/__setattr__会调用_local_get_dict先根据当前 greenlet 的id去impl.dicts里查找如果该 greenlet 还没有字典则调用_localimpl_create_dict新建并执行self.__init__(*impl.localargs, **impl.localkwargs)完成初始化见 src/gevent/local.py。三、子类化 local默认值、方法与初始化local类可以被继承这是它在实际项目中最常用的形态。通过子类可以定义默认属性值、方法以及初始化逻辑 class MyLocal(local): ... number 2 ... initialized False ... def __init__(self, **kw): ... if self.initialized: ... raise SystemError(__init__ called too many times) ... self.initialized True ... self.__dict__.update(kw) ... def squared(self): ... return self.number ** 2关键语义如果你定义了__init__那么每当 local 对象被一个新的 greenlet 使用时__init__都会再被调用一次。这是为了初始化那个新 greenlet 的属性字典——每个 greenlet 都需要独立的初始化状态。这正是上面示例中initialized标记存在的意义用来检验每个 greenlet 里__init__恰好只执行一次。创建子类实例并验证默认值、初始化参数与方法 mydata MyLocal(colorred) mydata.number 2 mydata.color red del mydata.color mydata.squared() 4在其他 greenlet 中访问时每个 greenlet 都会得到自己的一份初始状态 log [] greenlet gevent.spawn(f) greenlet.join() log [[(color, red), (initialized, True)], 11]新 greenlet 里的字典包含(color, red)来自构造参数和(initialized, True)来自__init__而原来的 greenlet 数据不受影响 mydata.number 2 mydata.color Traceback (most recent call last): ... AttributeError: MyLocal object has no attribute colorcolor之前已被del掉删除后读取就回落到类型层面的AttributeError。关于__init__与锁的版本变化从 gevent 1.3a1 开始见模块文档字符串的versionchanged记录子类local的__init__不再在持有锁的情况下被调用——CPython 原生实现也没有这样的锁。这可能表现为一个差异如果代码在__init__期间依赖__slots__中的多个相互关联属性这些属性是跨所有 greenlet 共享的并在其间发生 greenlet 切换行为会与旧版本不同。编写依赖共享 slot 状态的__init__时需要注意这一点。类型属性、描述符与实例字典的优先级子类化之后属性的查找逻辑变得复杂local的__getattribute__需要协调“实例字典greenlet 局部”与“类型类属性 / 描述符跨 greenlet 共享”两套体系。从源码src/gevent/local.py可以归纳出如下优先级规则属性在实例字典中且类型中不存在→ 直接返回字典值属性同时在类型与字典中若类型中是数据描述符定义了__get__且定义了__set__或__delete__类型胜出若是非数据描述符只定义__get__或普通属性实例字典胜出属性不在字典但在类型中 → 从类型的 MRO 中解析支持property、staticmethod、classmethod等描述符都不存在 → 调用type(self).__getattr__如果定义了否则抛AttributeError。创建实例时_local_find_descriptors会扫描类型的 MRO预先分类出 get/set/delete 描述符集合src/gevent/local.py从而在热路径上避免重复扫描 MRO这也是 1.3a1 性能优化的基础详见下文版本变迁一节。四、__slots__不是 greenlet 局部的子类可以定义__slots__但slot 是跨 greenlet 共享的并不会被隔离 class MyLocal(local): ... __slots__ number mydata MyLocal() mydata.number 42 mydata.color redcolor会被放进该 greenlet 的局部字典slot 之外的新属性而number是共享 slot。因此别的 greenlet 对number的修改会直接影响我们看到的 greenlet gevent.spawn(f) greenlet.join() mydata.number 11示例中f会把mydata.number设为 11。测试 test__local.py 中的test_slot_and_type_attributes也验证了这一系列行为slot 在另一个线程/greenlet 中被修改后原处可见slot 值优先于同名字典值删除 slot 不会删除字典条目等。如果确实需要“每个 greenlet 独立”的槽位语义应当把这些数据放在__dict__中而不是放进__slots__。五、生命周期与垃圾回收三个版本里程碑模块文档字符串记录了三个关键的版本变更它们共同塑造了当前gevent.local的内存行为1.1a2实现从 Python 2.5 的旧版更新为与 Python 3.4 一致。其结果是local 对象在所属 greenlet 退出后立即可以被垃圾回收不再被陈旧引用拖住。1.2.3在 local 对象先于 greenlet 消亡时改用弱引用来清除与 greenlet 之间建立的链接避免引用泄漏。1.3a1属性访问方法被直接实现并就地处理描述符从而移除了锁的使用显著提升了性能即上文第三、四节描述的查找逻辑。弱引用清理机制的实现细节从源码看清理机制由两个弱引用回调类协作完成src/gevent/local.py_greenlet_deleted当某个 greenlet 消亡时从_localimpl.dicts中弹出该 greenlet 的条目。若 greenlet 是gevent.greenlet.Greenlet且提供rawlink方法则用rawlink注册回调避免再 spawn 新 greenlet否则用weakref.ref(greenlet, callback)。_local_deleted当_localimpl自身被回收时解除 greenlet 上的rawlink回调并删除 greenlet__dict__中的key条目。dicts本身使用_wrefdict一个可被弱引用的 dict 子类dicts与greenlet.__dict__之间通过弱引用链互相引用保证不构成硬引用环。测试 test__local.py 中有专门的用例验证这些行为test_locals_collected_when_greenlet_dead_but_still_referenced对应 issue #387greenlet 已结束但仍被引用时local 数据也应被回收test_locals_collected_when_unreferenced_even_in_running_greenlet对应 issue #981即使在 greenlet 仍在运行、只是 local 对象不再被引用的情况下其数据也能被 GC 回收且 greenlet 上的链接被移除has_links()为 False。六、在 gevent 生态中的实际应用gevent.local不是孤立的工具它构成了多个关键模块的基础1. 线程本地存储的替代gevent.thread / gevent.threadingsrc/gevent/thread.py 中from gevent.local import local as _local用 greenlet-local 存储实现“当前线程/绿色线程”粒度的数据src/gevent/threading.py 同样导入gevent.local.local为 monkey-patch 后的threading模块提供正确的局部存储语义。2. monkey-patch 标准库_threading_localsrc/gevent/_patcher.py 的模块映射表中注册了gevent.local: _threading_localsrc/gevent/monkey/_patch_thread_common.py 的patch__threading_local方法将_threading_local.local直接替换为gevent.local.local。这意味着调用gevent.monkey.patch_all()之后所有使用threading.local的第三方代码包括 src/gevent/tests/test__local.py 中from threading import local的测试都会自动获得 greenlet 粒度的隔离语义。3. contextvars 的上下文存储PEP 567src/gevent/contextvars.py 中定义了class _ContextState(local)并用 greenlet-local 对象保存“当前上下文”Context。其注释明确指出参考实现把上下文指针存在解释器级 OS 线程状态中而 gevent 无法也不需要触碰 CPython 内部因此改用 greenlet-local 对象来存放活跃上下文从而让上下文变量contextvars.ContextVar在每个 greenlet 中独立生效。七、调试助手all_local_dicts_for_greenlet模块还提供了一个内部调试辅助函数all_local_dicts_for_greenlet(greenlet)自 1.3a2 加入src/gevent/local.pyfrom gevent.local import all_local_dicts_for_greenlet它返回((type, id), {})对的列表第一项是 local 对象的类型与id第二项是该 greenlet 视角下的实例字典。其实现遍历 greenlet 的__dict__找出所有key_prefix开头的条目解出_localimpl并取出对应条目。文档字符串明确说明它随时可能被变更或移除属于诊断/测试用途。对应测试test_local_dicts_for_greenletsrc/gevent/tests/test__local.py验证了其输出格式[((local, id_x), {foo: 42})]。该函数也被 src/gevent/util.py 引用用于 greenlet 运行状态的检查。八、性能Cython 加速与纯 Python 回退gevent.local具有双实现策略纯 Python 实现位于 src/gevent/local.py其类型声明文件为 src/gevent/_gevent_clocal.pxd对应的 Cython 加速模块名为gevent._gevent_clocal模块末尾通过import_c_accel(globals(), gevent._local)尝试加载加速版本见 src/gevent/local.py。Cython 版本通过cdef把_localimpl、_localimpl_dict_entry等内部结构声明为cython.final的扩展类型并将属性访问、字典创建等热路径函数cdef化避免 Python 层的方法调用与 vtable 开销。注释中还解释了为何使用模块级函数而非类方法见 src/gevent/local.py函数可以被cdef化而不会为每个实例引入方法指针与 vtable。两个实现的模块名不同gevent.localvsgevent._gevent_clocal因此代码用local.__module__判断当前处于哪种实现并据此决定是否注入__new__Cython 使用__cinit__而纯 Python 需要__new__见 src/gevent/local.py。测试 test__local.py 中的TestCExt与TestPure分别断言两种实现下local.__module__的取值。九、测试验证行为契约一览src/gevent/tests/test__local.py 在monkey.patch_all()之后以threading.local的名义测试注释说明“去掉这一行就能看到标准 thread.local 是否工作正常”其用例覆盖了本文所述的全部契约行为契约对应测试未子类化的 local 对象直接读写属性test_local_opts_not_subclassed__dict__只读禁止赋值/删除test_cannot_set_delete_dict删除不存在的属性抛AttributeError含跨线程test_delete_with_no_dictslot 跨 greenlet 共享、优先于字典值test_slot_and_type_attributes类型属性可读、可在实例字典中覆盖并删除回退test_class_attr/test_class_attr_subclassproperty数据描述符与只读属性test_slot_and_type_attributes、MyLocal.descstaticmethod/classmethod正常工作test_local_with_staticmethod/test_local_with_classmethodMRO 中含 ABCcollections.abc.Mapping不破坏行为test_local_with_abcissue #1201zope.interface 的providedBy可用test_providesissue #1122copy.copy深/浅拷贝语义浅拷贝属性、独立字典test_copy/test_copy_no_subclassgreenlet 消亡或对象失引用后数据可被 GCtest_locals_collected_*issue #387 / #981all_local_dicts_for_greenlet输出格式test_local_dicts_for_greenlet这些测试共同构成gevent.local的行为契约也是理解各边界条件的最佳参考。十、小结gevent.local是 gevent 中“绿色线程本地存储”的标准答案隔离粒度按 greenlet 隔离实例字典跨 greenlet 互不可见定制能力通过子类定义默认值、方法与每个 greenlet 独立执行的__init__共享与隔离并存类型属性、描述符与__slots__跨 greenlet 共享实例字典按 greenlet 隔离健壮的内存管理弱引用 rawlink回调保证 greenlet 退出或 local 对象失引用后数据及时回收广泛落地支撑gevent.thread、gevent.threading、gevent.contextvars并在 monkey-patch 后无缝接管_threading_local性能双轨Cython 加速实现与纯 Python 回退并存无锁描述符解析带来高效热路径。无论是编写 greenlet 内隔离的请求上下文、连接绑定还是理解 gevent 上下文变量与线程补丁的实现掌握gevent.local都是关键的一环。赞分享后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载相关推荐Python-Greenlet项目解析Greenlet与Python线程的交互机制Python Greenlet项目解析Greenlet与Python线程的交互机制 前言 在Python并发编程领域greenlet提供了一种轻量级的协程实ChatterBot-Corpus打造多语言对话机器人的终极训练数据指南ChatterBot Corpus打造多语言对话机器人的终极训练数据指南 ChatterBot Corpus 是一个多语言对话语料库A multilingu数据集NLP为什么选择openWakeWord5大核心优势让语音交互更精准高效为什么选择openWakeWord5大核心优势让语音交互更精准高效 openWakeWord是一款专注于性能与简洁性的开源音频唤醒词或短语检测框架为开发上一篇文本两端对齐Text Justification贪心解法全解析以 leetcode 仓库 68 题多语言实现为例下一篇pandas 表格数据读写入门read_* / to_* 函数族与 head、dtypes、info 数据检查实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表