ARTICLE DETAIL

资讯详情

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

gevent.lock 并发原语全解析:Semaphore、BoundedSemaphore、DummySemaphore 与 RLock 的源码级指南

gevent.lock 并发原语全解析:Semaphore、BoundedSemaphore、DummySemaphore 与 RLock 的源码级指南 后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载导读gevent.lock是 gevent 提供的协程级锁原语模块为绿色协程greenlet场景重新实现了信号量Semaphore与可重入锁RLock同时针对多线程混用、公平唤醒、C 扩展加速等做了专门的工程化处理。阅读完本文你将掌握四大并发原语的完整 API、上下文管理器用法、在 gevent 自身线程池、连接池、文件对象中的实际应用模式以及其与标准库threading锁在实现与语义上的关键差异。说明docs/api/gevent.lock.rst是本文的主体依据它通过automodule自动渲染 src/gevent/lock.py 中的类文档文中所有 API 细节、版本变更与实现原理均以该模块及 src/gevent/_semaphore.py 的源码与 src/gevent/tests/test__semaphore.py、src/gevent/tests/test__lock.py 的测试为准。模块概览四个公开原语gevent.lock通过__all__导出四个公开类见 src/gevent/lock.py类语义适用场景Semaphore任意上限的信号量计数 release 次数 − acquire 次数 初始值限制并发访问有限资源默认 value1 时为互斥锁BoundedSemaphore更安全的信号量防止过度 release大多数用户应优先选择能提前暴露计数失衡的 bugDummySemaphore“无限容量”的信号量任何方法都永不阻塞参数化“是否需要真正加锁”资源无上限或对象本身线程安全时使用RLock可重入互斥锁API 与threading.RLock一致同一 greenlet 需要多次进入临界区需要特别强调的是所有 gevent 内部代码、测试乃至用户代码都只能从gevent.lock导入这些原语而绝不要直接导入gevent._semaphore。后者仅作为 Cython 可单独编译的实现单元存在其模块头注释明确写道“This is not the place to import from”见 src/gevent/_semaphore.py测试文件 src/gevent/tests/test__semaphore.py 也重复了这一约定。Semaphore基础信号量构造与语义from gevent.lock import Semaphore s Semaphore(value1) # value 默认为 1信号量维护一个计数器其值等于release调用次数减去acquire调用次数再加上初始值。acquire在必要时会阻塞直到返回时计数器不会变为负数。与线程锁不同信号量不追踪 greenlet 所有权——任何 greenlet 都可以调用release即使它从未acquire过见 src/gevent/_semaphore.py。构造时如果value为负数会抛出ValueError(semaphore initial value must be 0)src/gevent/_semaphore.py。核心方法acquire(blockingTrue, timeoutNone) - boolblockingTrue默认时阻塞直到获得信号量timeout为浮点秒数指定最大阻塞时间仅当blockingTrue时有效返回值True表示成功获取如果blockingTrue, timeoutNone且初始值大于 0则永远返回True若超时未获得则返回False。注意极端情况若别的调用方已经启动了计时器本方法仍可能抛出Timeout异常src/gevent/_semaphore.py若以value0初始化且blockingTrue, timeoutNone将永久阻塞除非有超时或blockingFalse。release()计数器加 1并通知等待者无返回值。文档明确提示过度 release 是允许的释放次数超过 acquire 次数与初始值之和这通常是 bug 的征兆但在某些场景下可被刻意利用例如“建模额外资源的到达”src/gevent/_semaphore.py。wait(timeoutNone) - int等待直到可以获取信号量或超时。返回值是一个整数表示在阻塞之前还能获取多少次——这个数字可能是 0例如其他等待者抢到了。若以value0初始化且不传超时将永远阻塞src/gevent/_semaphore.py。状态查询locked()返回信号量是否“不可获取”即counter 0对二进制信号量最有用ready()返回counter 0即是否可以获取src/gevent/_semaphore.py。上下文管理器信号量实现了__enter__/__exit__可直接用于with语句from gevent.lock import Semaphore sem Semaphore(2) with sem: # 进入时 acquire退出时 release # 最多允许 2 个 greenlet 同时进入 pass一个值得一提的细节该类的__exit__在 CPython 下不会调用 trace 函数但在 PyPy 下会src/gevent/_semaphore.py。等待者唤醒顺序与公平性唤醒顺序的语义在历次版本中有明确变更1.4.0 起文档明确“等待者唤醒顺序未指定”此前因 CPython 实现细节通常表现为 FIFO1.5a3 起等待中的 greenlet 按等待顺序FIFO被唤醒src/gevent/_semaphore.py1.5a3 同时规定底层rawlink方法会在调用回调前自动 unlink 等待者。这一公平性有测试直接验证TestSemaphoreFair.test_fair_or_hangs见 src/gevent/tests/test__semaphore.py构造了三个绿色协程相互链式 acquire/release 的竞争场景如果锁不公平就会在最后两个 greenlet 之间无限自旋对应 issue #1487测试通过预期抛出LoopExit并核对各 greenlet 的存活状态来确认公平性。多线程使用的演进20.12.0 / 24.2.1gevent.lock.Semaphore不仅服务于单线程内的绿色协程切换还经过了专门的跨线程加固20.12.0改进多线程使用支持。检测到跨线程使用时实例不再为不存在的线程 hub 强行创建它src/gevent/_semaphore.py。这一改动至关重要因为 Semaphore 被importlib.ModuleLock使用而后者在导入 hub 本身的过程中就会出现——绝不能在此处强制创建 hubsrc/gevent/_semaphore.py24.2.1跨线程操作改用 Python 3 原生锁超时机制取代原先的自旋等待src/gevent/_semaphore.py。从实现看acquire会记录首次获取它的线程标识_multithreaded一旦发现不同线程访问便标记为_MULTI跨线程阻塞时根据“是否存在两个 hub”选择不同的路径双 hub 场景用 async watcher 通知__acquire_using_two_hubs无 hub 场景用原生线程锁__acquire_without_hubs单 hub 场景则通过run_callback_threadsafe委托给归属 hubsrc/gevent/_semaphore.py。对应的多线程测试位于 src/gevent/tests/test__semaphore.py包括test_acquire_in_one_then_another主线程持有、工作线程等待后释放、test_acquire_in_one_then_another_timed等待线程超时放弃、test_dueling_threads与test_dueling_threads_with_hub两个线程无其他绿色协程可切换时反复 acquire/release 一万次以及TestBoundedSemaphoreMultiThread对 Bounded 版本的同样验证。关于 C 扩展与纯 Python 实现gevent._semaphore是可通过 Cython 单独编译的.py文件编译产物为gevent._gevent_c_semaphore。文件末尾通过import_c_accel尝试加载 C 加速版本src/gevent/_semaphore.py。测试 src/gevent/tests/test__semaphore.py 会校验在非纯 Python 模式下Semaphore.__module__确为gevent._gevent_c_semaphore。在纯 Python 模式PURE_PYTHON或 PyPy 下gevent.lock会改用它内部的_AtomicSemaphore/_AtomicBoundedSemaphore包装类通过自有的_GILLock模拟“方法全程持有 GIL”的原子性语义确保与 Cython 版本行为一致见 src/gevent/lock.py 与 src/gevent/lock.py。注释中还记录了 PyPy 下纯 Python 版本比 Cython 版本快数倍这一反直觉现象micro-benchmark 约 1s 对 4s以及历史上 Cython 版本在 PyPy ≤ 4.0.1 的 GC 交互崩溃问题src/gevent/_semaphore.py。BoundedSemaphore防过度释放的安全信号量from gevent.lock import BoundedSemaphore bs BoundedSemaphore(value1) # value 默认为 1BoundedSemaphore检查当前值不会超过初始值若过度释放当前值已达到初始值仍调用release抛出ValueErrorsrc/gevent/_semaphore.py。由于大多数场景中信号量用于守护有限容量的资源释放次数超出 acquire 次数 初始值通常是明显的编程 bug——这正是官方建议“拿不准时优先选择 BoundedSemaphore”的原因src/gevent/_semaphore.py。两个值得注意的实现细节_OVER_RELEASE_ERROR类属性用于 monkey-patching 时替换抛出的异常类型src/gevent/_semaphore.py_at_fork_reinit在 fork 后会把计数器重置为初始值配合BoundedSemaphore的 hub 释放逻辑release后若计数器回到初始值则解除 hub 绑定提升跨线程/跨进程使用安全src/gevent/_semaphore.py。上下文管理器与线程安全同样适用BoundedSemaphore继承自Semaphore具备相同的with用法、wait/locked/ready方法与多线程支持。DummySemaphore永不阻塞的“替身锁”from gevent.lock import DummySemaphore d DummySemaphore(valueNone) # 1.1rc3 起接受并忽略 value 参数以兼容 SemaphoreDummySemaphore拥有与Semaphore相同的 API但以“无限”初始值初始化任何方法都永不阻塞见 src/gevent/lock.py 的完整文档locked()永远返回Falseready()永远返回Truerelease()什么也不做wait(timeoutNone)立即返回 1acquire(blockingTrue, timeoutNone)忽略所有参数1.1a1 起始终返回True作为上下文管理器使用时进入/退出都是空操作。它的核心价值在于参数化“是否真的需要加锁”src/gevent/lock.py资源确实有限如固定大小的线程池→ 用真正的Semaphore资源无上限 → 用DummySemaphore从而支持代码完全不变底层对象已知线程安全、无需互斥 → 用DummySemaphore否则用真正的Semaphore。gevent 内部正是如此使用的src/gevent/lock.pysrc/gevent/pool.pyPool在sizeNone不限制大小时用DummySemaphore作为_semaphore否则用Semaphore(size)src/gevent/_fileobjectcommon.pyFileObject系列根据参数决定用Semaphore()还是DummySemaphore()来保护底层文件 I/O文档也明确允许用户传入自己的gevent.lock.Semaphore实例src/gevent/_fileobjectcommon.py。RLock可重入互斥锁from gevent.lock import RLock lock RLock(hubNone) # hub 参数 20.5.1 起可用 with lock: with lock: # 同一 greenlet 可重复进入 passRLock是“同一 greenlet 可多次 acquire 的互斥锁”任意时刻只能被一个 greenlet 持有同一个 greenlet 可以多次acquire但每次acquire必须配对一次release未 acquire 过该锁的 greenlet 调用release是错误会抛出RuntimeError实例是上下文管理器src/gevent/lock.py。方法细节acquire(blockingTrue, timeoutNone)blockingTrue时阻塞最长timeout秒timeout 参数 1.5a4 起新增返回布尔值表示是否获取成功若当前 greenlet 已是持有者则内部计数_count加 1 并立即返回 1可重入的核心逻辑见 src/gevent/lock.py。release()只有最初获取锁的 greenlet 可以释放否则抛出RuntimeError(cannot release un-acquired lock. ...)内部计数减到 0 时清除持有者并释放底层信号量src/gevent/lock.py。locked()返回当前是否被锁定_count 0该方法为25.4.1 版本新增src/gevent/lock.py。实现方式与内部接口RLock的实现非常简洁内部委托给一个Semaphore(1, hub)作为互斥基元外加_owner当前持有 greenlet与_count重入深度两个状态src/gevent/lock.py。因此它天然继承了 Semaphore 的协程友好与多线程安全特性。它还暴露了三个“供条件变量内部使用”的方法_acquire_restore(count_owner)、_release_save()与_is_owned()用于在不丢失锁状态的前提下临时保存/恢复所有权src/gevent/lock.py。多线程场景下RLock复用信号量的跨线程测试TestRLockMultiThread直接继承test__semaphore.TestSemaphoreMultiThread见 src/gevent/tests/test__lock.py并特意不在测试前显式设置 hub以覆盖“后台线程首次接触锁时才决定归属”的竞态路径。fork 场景则由TestLockReinitAfterFork覆盖通过子进程脚本在回调运行期间 fork 并反复 acquire/release对应 issue #1895验证不再出现断言失败输出src/gevent/tests/test__lock.py。在 gevent 内部的使用RLock被 gevent 自身广泛使用src/gevent/threading.py 与 src/gevent/builtins.py 引入RLock作为threading.RLock的绿色协程替代品参与 monkey-patching从源码结构看凡是需要在同一 greenlet 内递归获取的锁场景如重入保护的资源访问都以RLock为首选。在真实项目中的典型应用模式1. 连接/资源池限流Poolsrc/gevent/pool.py 展示了最经典的信号量用法Pool(size)用Semaphore(size)控制最大并发 greenlet 数sizeNone时不限流并退化为DummySemaphoresize0则创建一个永远无法 spawn 的池除非配合wait_available超时与free_count检查。wait_available的实现本质上就是等待信号量可获取。2. 写锁与线程池配额src/gevent/server.pyStreamServer用Semaphore()二进制信号量实现写锁_writelocksrc/gevent/threadpool.pyThreadPool导入Semaphore限制排队任务配额其注释同时提醒gevent.lock.Semaphore在单线程内使用才完全安全见 src/gevent/threadpool.pysrc/gevent/thread.pyBoundedSemaphore被用作线程内互斥原语。3. 与事件/结果对象同族Semaphore继承自AbstractLinkablesrc/gevent/_abstract_linkable.py与Event、AsyncResult共享“可链接linkable”基础设施rawlink(callback)注册就绪回调、unlink(callback)撤销src/gevent/_abstract_linkable.py_check_and_notify在就绪且有链接时调度通知src/gevent/_abstract_linkable.py。这也解释了为何Semaphore可以直接传给gevent.wait([s])等待其就绪——测试 src/gevent/tests/test__semaphore.py 验证了这一点对应 issue #1287。快速决策指南你的需求选择只做互斥单 greenlet 内不会重入Semaphore(1)互斥且可能递归进入临界区RLock限制并发数量 NSemaphore(N)限制并发数量且想尽早暴露“释放过多”的 bugBoundedSemaphore(N)官方推荐默认选项资源无上限 / 对象本身线程安全只是不想改代码DummySemaphore导入规范提醒一律from gevent.lock import Semaphore, BoundedSemaphore, DummySemaphore, RLock不要从gevent._semaphore导入否则在纯 Python/PyPy 环境下会绕过_AtomicSemaphore包装得不到与 Cython 编译版本一致的原子性保证。版本要点速查1.1a1DummySemaphore.acquire始终返回True1.1rc3DummySemaphore接受并忽略value参数1.4.0明确等待者唤醒顺序未指定1.5a3等待者按等待顺序FIFO唤醒rawlink自动 unlinkRLock.acquire新增timeout20.5.1RLock新增hub参数20.12.0Semaphore 改进多线程支持不再为不存在的线程 hub 强建实例24.2.1跨线程操作使用 Python 3 原生锁超时替代自旋25.4.1RLock.locked()新增。如需继续深入可对照阅读 src/gevent/lock.py、src/gevent/_semaphore.py、src/gevent/_abstract_linkable.py以及两个测试文件 src/gevent/tests/test__semaphore.py 与 src/gevent/tests/test__lock.py 中的完整用例。赞分享后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载相关推荐Podman 仓库中的 modern-go/concurrent 并发原语解析Map 与 Executor 的源码级指南Podman 仓库中的 modern go/concurrent 并发原语解析Map 与 Executor 的源码级指南 本文基于 Podman 仓库 ven容器运行时云原生CLIlinux-insides 源码解析Linux 内核信号量Semaphore同步原语完全指南linux insides 源码解析Linux 内核信号量Semaphore同步原语完全指南 本篇是开源书籍《linux insides》 同步原语章节文档教程操作系统CPython asyncio 同步原语全解析Lock、Event、Condition、Semaphore 与 Barrier 的用法与源码实现CPython asyncio 同步原语全解析Lock、Event、Condition、Semaphore 与 Barrier 的用法与源码实现 本指南以 C编程语言语言运行时解释器标准库上一篇终极B站视频批量下载指南Bilidown让8K高清收藏变得简单下一篇Thorium浏览器重新定义Chromium性能极限的终极优化方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表