Python RLock、Semaphore 的适用场景
发布时间 - 2026-01-30 00:00:00 点击率:次RLock适用于同一线程需多次获取同一锁的递归或重入场景,允许重复acquire并需对应次数release;Semaphore则用于控制并发数,允许多线程共享资源但限制总量,acquire与release可跨线程。
RLock 适合递归加锁的线程安全场景
当一个线程需要多次获取同一把锁(比如在递归调用、重入方法中),用普通 Lock 会直接阻塞自己,导致死锁。而 RLock(可重入锁)允许同一线程重复调用 acquire(),只要对应次数的 release() 就能真正释放锁。
常见错误现象: threading.Lo 在递归函数里卡住,CPU 占用低但程序不继续;报错信息里没有异常,只是静默挂起。
使用场景:
- 类方法中调用自身其他加锁方法(如
add()内部调用update(),两者都需保护共享状态) - 实现线程安全的缓存装饰器,内部可能多次访问被锁保护的字典
- 构造复杂对象时,初始化过程嵌套调用多个需同步的子步骤
注意点:
-
RLock比Lock开销略大,因为要维护持有线程 ID 和计数器 - 必须由同一个线程完成所有
acquire()和release(),跨线程调用release()会抛RuntimeError: release unlocked lock - 不解决“锁顺序不一致”导致的死锁,只解决“自己锁自己”的问题
Semaphore 更适合资源池或并发数控制
Semaphore 的核心不是“互斥”,而是“限制同时访问的线程数量”。它内部维护一个计数器,acquire() 减一,release() 加一,计数器为 0 时阻塞。
典型使用场景:
- 控制对数据库连接池、HTTP 客户端、GPU 显存等有限资源的并发访问
- 限流:比如最多允许 5 个线程同时执行耗时任务,其余排队
- 模拟生产者-消费者中“槽位”数量(比
Queue更轻量,但不带数据传递)
和 Lock/RLock 的关键差异:
-
acquire()可由 A 线程调用,release()可由 B 线程调用(即不要求同线程) - 初始化时传入的数值就是最大并发数,设为 1 时行为接近
Lock,但语义不同、开销略高 - 不保证临界区的排他性——多个线程可以同时在临界区内,只要没超限额
容易踩的坑:
- 忘记调用
release(),导致计数器永远卡在 0,后续所有线程永久阻塞 - 在异常路径中未做
try/finally保护,造成资源泄漏(建议用上下文管理器) - 误以为
Semaphore能替代条件变量,其实它不提供“等待某个状态成立”的能力
别用 RLock 去代替 Semaphore 控制并发数
有人看到 RLock 也能“控制进入”,就试图靠它限制并发线程数,这是错的。因为 RLock 的重入特性意味着:只要一个线程拿到锁,它就能无限次 acquire(),完全绕过限制。
示例问题代码:
lock = threading.RLock() # 错误:下面这段对并发数毫无约束力 lock.acquire() do_work() lock.release()
这和用 Lock 效果一样,且更易引发隐藏 bug。真正要控并发,请明确用 Semaphore(value=3)。
另一个混淆点:BoundedSemaphore 是 Semaphore 的子类,会在 release() 超出初始值时抛异常,适合调试阶段捕获误释放。
实际选型看“谁该放行”和“放行依据”
决定用哪个,关键是回答两个问题:
- 放行依据是“是不是同一个线程”?→ 选
RLock - 放行依据是“当前已占用资源数是否达到上限”?→ 选
Semaphore - 需要严格一对一加锁/解锁,且不允许跨线程释放?→ 只能用
Lock或RLock
性能上差异不大,但语义错位会导致极难复现的竞态。比如在连接池里误用 RLock,可能让单个线程占满所有连接却不释放,而其他线程干等。
最常被忽略的一点:Python 的 GIL 让纯计算密集型任务无法真正并行,所以这些同步原语主要保护的是 I/O、共享数据结构(如 dict、list)、或 C 扩展中释放 GIL 后的临界区——别在无共享的 CPU 绑定循环里白加锁。
# python
# 递归函数
# 一加
# 并发访问
# 子类
# try
# 递归
# 循环
# 数据结构
# finally
# 线程
# 多线程
# 并发
# 对象
# 数据库
# http
# bug
# 死锁
# 加锁
# 就能
# 多个
# 如在
# 可由
# 的是
# 这是
# 连接池
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
如何在橙子建站上传落地页?操作指南详解
Laravel怎么清理缓存_Laravel optimize clear命令详解
Python函数文档自动校验_规范解析【教程】
Laravel队列任务超时怎么办_Laravel Queue Timeout设置详解
Linux安全能力提升路径_长期防护思维说明【指导】
香港服务器网站推广:SEO优化与外贸独立站搭建策略
Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】
Python3.6正式版新特性预览
阿里云高弹*务器配置方案|支持分布式架构与多节点部署
如何在云主机上快速搭建多站点网站?
Android实现代码画虚线边框背景效果
如何用IIS7快速搭建并优化网站站点?
html5怎么画眼睛_HT5用Canvas或SVG画眼球瞳孔加JS控制动态【绘制】
如何快速生成可下载的建站源码工具?
使用Dockerfile构建java web环境
html5如何实现懒加载图片_ intersectionobserver api用法【教程】
Laravel怎么实现软删除SoftDeletes_Laravel模型回收站功能与数据恢复【步骤】
Java遍历集合的三种方式
大连网站制作公司哪家好一点,大连买房网站哪个好?
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
网站制作报价单模板图片,小松挖机官方网站报价?
Laravel如何构建RESTful API_Laravel标准化API接口开发指南
Laravel如何获取当前用户信息_Laravel Auth门面获取用户ID
laravel怎么通过契约(Contracts)编程_laravel契约(Contracts)编程方法
如何实现javascript表单验证_正则表达式有哪些实用技巧
三星网站视频制作教程下载,三星w23网页如何全屏?
Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析
java获取注册ip实例
千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】
高防服务器如何保障网站安全无虞?
Laravel如何实现API版本控制_Laravel版本化API设计方案
java ZXing生成二维码及条码实例分享
韩国服务器如何优化跨境访问实现高效连接?
中山网站制作网页,中山新生登记系统登记流程?
常州企业网站制作公司,全国继续教育网怎么登录?
Laravel如何实现用户注册和登录?(Auth脚手架指南)
PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)
Laravel如何实现本地化和多语言支持?(i18n教程)
laravel怎么为API路由添加签名中间件保护_laravel API路由签名中间件保护方法
在Oracle关闭情况下如何修改spfile的参数
Laravel请求验证怎么写_Laravel Validator自定义表单验证规则教程
Laravel如何配置Horizon来管理队列?(安装和使用)
百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧
JavaScript中如何操作剪贴板_ClipboardAPI怎么用
制作公司内部网站有哪些,内网如何建网站?
弹幕视频网站制作教程下载,弹幕视频网站是什么意思?
如何在阿里云服务器自主搭建网站?
油猴 教程,油猴搜脚本为什么会网页无法显示?
php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】

