Python BoundedSemaphore 有界信号量详解
Python BoundedSemaphore 有界信号量详解一、Python BoundedSemaphore 有界信号量详解1、 引言2、信号量基础回顾2.1、 什么是信号量2.2、 普通 Semaphore 的问题3、 BoundedSemaphore 的原理3.1、 有界约束3.2、 源码实现4、基本用法4.1、 标准「获取-释放」模式4.2、 使用上下文管理器4.3 、一次获取多个许可3.135、BoundedSemaphore 与 Semaphore 对比6、 实际应用场景6.1、 数据库连接池6.2、 限流器6.3、 生产者-消费者模型7、常见陷阱与最佳实践7.1、 忘记释放导致死锁7.2、 重复释放导致 ValueError7.3 、与 asyncio 的区分8、性能考量9、 总结二、代码示例一、Python BoundedSemaphore 有界信号量详解1、 引言在多线程与多进程编程中信号量Semaphore是一种经典的同步原语用于控制对共享资源的并发访问数量。Python 标准库threading模块提供了Semaphore与BoundedSemaphore两个类其中BoundedSemaphore在Semaphore的基础上增加了「有界」约束能有效防止因release()调用次数超过acquire()而导致的信号量计数无限增长。本文基于 Python 最新稳定版本3.12部分特性在 3.13 中进一步增强深入讲解BoundedSemaphore的原理、用法、与普通Semaphore的区别以及实际应用场景。2、信号量基础回顾2.1、 什么是信号量信号量内部维护一个计数器调用acquire()时若计数器大于 0则将其减 1 并继续执行若计数器为 0则阻塞等待。调用release()时将计数器加 1并唤醒一个等待中的线程。importthreading semthreading.Semaphore(2)# 允许 2 个并发defworker():sem.acquire()try:# 临界区passfinally:sem.release()2.2、 普通 Semaphore 的问题普通Semaphore允许release()被任意多次调用即使没有对应的acquire()。这会导致计数器不断增大超出初始值从而失去「限制并发数」的意义semthreading.Semaphore(2)sem.release()# 计数变为 3sem.release()# 计数变为 4# 并发上限被悄悄放大3、 BoundedSemaphore 的原理3.1、 有界约束BoundedSemaphore继承自Semaphore唯一区别在于当release()导致计数器超过初始值时会抛出ValueError。importthreading boundedthreading.BoundedSemaphore(2)bounded.release()# ValueError: Semaphore released too many times这一机制能帮助开发者尽早发现代码中的错误——例如release()被重复调用、或acquire()失败后仍执行了release()。3.2、 源码实现在 CPython 的threading.py中BoundedSemaphore的实现非常简洁classBoundedSemaphore(Semaphore):def__init__(self,value1):super().__init__(value)self._initial_valuevaluedefrelease(self,n1):withself._cond:ifself._valuenself._initial_value:raiseValueError(Semaphore released too many times)returnsuper().release(n)注意在 Python 3.13 中Semaphore.release()新增了n参数支持一次释放多个许可BoundedSemaphore同步支持该参数并在释放后校验是否越界。4、基本用法4.1、 标准「获取-释放」模式importthreadingimporttime# 最多允许 3 个线程同时访问boundedthreading.BoundedSemaphore(3)defworker(name):bounded.acquire()try:print(f{name}进入临界区)time.sleep(1)finally:bounded.release()threads[threading.Thread(targetworker,args(f线程-{i},))foriinrange(5)]fortinthreads:t.start()fortinthreads:t.join()4.2、 使用上下文管理器Semaphore支持上下文管理器协议可简化代码并确保异常时也能正确释放boundedthreading.BoundedSemaphore(2)defworker(name):withbounded:print(f{name}正在执行)time.sleep(0.5)4.3 、一次获取多个许可3.13boundedthreading.BoundedSemaphore(5)# 一次获取 3 个许可bounded.acquire(3)try:# 需要 3 个资源的操作passfinally:bounded.release(3)5、BoundedSemaphore 与 Semaphore 对比特性SemaphoreBoundedSemaphore计数可超过初始值是否多余 release 行为静默增加计数抛出ValueError适用场景需要动态扩展并发上限严格限制并发数、排查错误性能开销略低几乎无差异仅多一次比较选择建议除非有特殊需求否则优先使用BoundedSemaphore它能在开发阶段暴露release()滥用问题。6、 实际应用场景6.1、 数据库连接池importthreadingimporttimeclassConnectionPool:def__init__(self,size):self._semthreading.BoundedSemaphore(size)self._connections[self._create_conn()for_inrange(size)]def_create_conn(self):returnobject()# 模拟数据库连接defget_conn(self):self._sem.acquire()try:returnself._connections.pop()exceptIndexError:# 实际项目中连接会被归还这里仅作演示self._sem.release()raisedefreturn_conn(self,conn):self._connections.append(conn)self._sem.release()6.2、 限流器importthreadingimporttimeclassRateLimiter:def__init__(self,max_concurrent):self._semthreading.BoundedSemaphore(max_concurrent)def__enter__(self):self._sem.acquire()def__exit__(self,exc_type,exc_val,exc_tb):self._sem.release()returnFalse# 使用limiterRateLimiter(5)withlimiter:# 最多 5 个并发请求pass6.3、 生产者-消费者模型importthreadingimportqueueimporttime boundedthreading.BoundedSemaphore(3)# 缓冲区最多 3 个defproducer(q):foriinrange(10):bounded.acquire()q.put(i)print(f生产:{i})time.sleep(0.2)defconsumer(q):whileTrue:itemq.get()ifitemisNone:breakprint(f消费:{item})bounded.release()q.task_done()qqueue.Queue()t1threading.Thread(targetproducer,args(q,))t2threading.Thread(targetconsumer,args(q,))t1.start()t2.start()t1.join()q.put(None)t2.join()7、常见陷阱与最佳实践7.1、 忘记释放导致死锁boundedthreading.BoundedSemaphore(1)defbad_worker():bounded.acquire()# 如果这里抛出异常且未用 try/finally信号量永远不会释放raiseRuntimeError(出错)解决始终使用with语句或try/finally。7.2、 重复释放导致 ValueErrorboundedthreading.BoundedSemaphore(1)bounded.acquire()bounded.release()bounded.release()# ValueError这正是BoundedSemaphore的价值所在——普通Semaphore会静默接受掩盖 bug。7.3 、与 asyncio 的区分threading.BoundedSemaphore是线程级同步原语会阻塞线程在异步代码中应使用asyncio.BoundedSemaphoreimportasyncioasyncdefmain():semasyncio.BoundedSemaphore(3)asyncwithsem:awaitasyncio.sleep(1)8、性能考量BoundedSemaphore基于threading.Condition实现在竞争激烈时存在一定的上下文切换开销。对于极高并发的场景可考虑使用asyncio.Semaphore协程级无线程切换使用第三方库如multiprocessing.BoundedSemaphore进程级使用无锁数据结构替代9、 总结BoundedSemaphore是 Python 中一个简单而强大的同步工具它继承了Semaphore的全部能力并增加了计数上限保护多余的release()会立即抛出ValueError帮助开发者尽早发现逻辑错误适用于连接池、限流、生产者-消费者等经典并发场景在 Python 3.13 中支持一次释放多个许可使用更加灵活。在实际项目中建议默认使用BoundedSemaphore而非Semaphore让并发边界更加清晰可控。二、代码示例importthreadingimporttimedefdemo_basic_limit():print(*70)print(【场景1BoundedSemaphore 并发限流最大允许2个线程同时执行】)# 初始上限 value2bsemthreading.BoundedSemaphore(value2)defworker(tid):withbsem:print(f线程{tid}获取到信号量开始工作)time.sleep(1.0)print(f线程{tid}释放信号量结束工作)threads[threading.Thread(targetworker,args(i,))foriinrange(4)]fortinthreads:t.start()fortinthreads:t.join()print(场景1执行完毕\n)defdemo_illegal_release():print(*70)print(【场景2核心特性未acquire直接release抛出ValueError普通Semaphore不会报错】)bsemthreading.BoundedSemaphore(2)# 正常获取释放bsem.acquire()bsem.release()print(✅ 正常 acquire → release 成功)try:# 没有上锁直接release计数超过初始值bsem.release()bsem.release()bsem.release()exceptValueErrorase:print(f❌ 捕获异常 ValueError:{e})print(场景2执行完毕\n)defdemo_non_blocking_and_timeout():print(*70)print(【场景3acquire 非阻塞 / 超时等待】)bsemthreading.BoundedSemaphore(1)# 主线程先占用bsem.acquire()deftry_get(tid):# 非阻塞获取ok1bsem.acquire(blockingFalse)print(f线程{tid}非阻塞获取结果:{ok1})# 超时等待0.5秒ok2bsem.acquire(timeout0.5)print(f线程{tid}等待0.5s获取结果:{ok2})tthreading.Thread(targettry_get,args(1,))t.start()t.join()bsem.release()print(场景3执行完毕\n)defdemo_manual_try_finally():print(*70)print(【场景4手动 acquire try‑finally 安全释放不使用with】)bsemthreading.BoundedSemaphore(1)deftask():acquiredFalsetry:acquiredbsem.acquire(timeout1)ifacquired:print(手动拿到信号量执行业务逻辑)time.sleep(0.3)else:print(获取信号量超时)finally:ifacquired:bsem.release()print(finally 中安全释放信号量)tthreading.Thread(targettask)t.start()t.join()print(场景4执行完毕\n)defcompare_sem_vs_bounded():print(*70)print(【场景5对比普通Semaphore和BoundedSemaphore区别】)importqueue semthreading.Semaphore(1)bsemthreading.BoundedSemaphore(1)print(普通Semaphore可以无限release)sem.release()sem.release()print(Semaphore多次release不会报错)print(\nBoundedSemaphore禁止超额release)try:bsem.release()exceptValueErroraserr:print(fBoundedSemaphore报错{err})print(场景5执行完毕\n)if__name____main__:demo_basic_limit()demo_illegal_release()demo_non_blocking_and_timeout()demo_manual_try_finally()compare_sem_vs_bounded()D:\user\01417804\桌面\PythonProject\.venv\Scripts\python.exe D:\user\01417804\桌面\PythonProject\main.py【场景1BoundedSemaphore 并发限流最大允许2个线程同时执行】 线程0获取到信号量开始工作 线程1获取到信号量开始工作 线程1释放信号量结束工作 线程2获取到信号量开始工作 线程0释放信号量结束工作 线程3获取到信号量开始工作 线程3释放信号量结束工作线程2释放信号量结束工作 场景1执行完毕【场景2核心特性未acquire直接release抛出ValueError普通Semaphore不会报错】 ✅ 正常 acquire → release 成功 ❌ 捕获异常 ValueError:Semaphore released too many times 场景2执行完毕【场景3acquire 非阻塞/超时等待】 线程1非阻塞获取结果:False线程1等待0.5s获取结果:False场景3执行完毕【场景4手动 acquiretry‑finally安全释放不使用with】 手动拿到信号量执行业务逻辑finally中安全释放信号量 场景4执行完毕【场景5对比普通Semaphore和BoundedSemaphore区别】 普通Semaphore可以无限release Semaphore多次release不会报错 BoundedSemaphore禁止超额release BoundedSemaphore报错Semaphore released too many times 场景5执行完毕 进程已结束退出代码为0

相关新闻

埃拉托斯特尼筛法全解析:从求第N个质数到工程级优化

埃拉托斯特尼筛法全解析:从求第N个质数到工程级优化

1. 从一道经典问题说起:求第 N 个质数到底难在哪如果你是刚接触算法不久,或者刷题时卡在“求第 100000 个质数”这种题目上,那你一定体会过那种“明明思路很简单,但一跑就超时”的挫败感。质数判定本身不复杂,教科书里…

2026/10/10 6:01:46 阅读更多 →
更新至2026年全球地缘政治风险指数GPR数据(日度+月度)

更新至2026年全球地缘政治风险指数GPR数据(日度+月度)

更新至2026年全球地缘政治风险指数GPR数据(日度月度) 1、时间:日度数据(1985.1.1-2026.5.18)、月度数据(1900.1-2026.4) 2、指标: 日度:DAY、Number of articles (10 recent newspapers, 198…

2026/10/10 6:01:46 阅读更多 →
Spring Boot整合Flyway

Spring Boot整合Flyway

一、前言 一般在我们开发项目的时候经常会更新数据库表的字段,如果同事 a 添加了表字段,没有及时给同事 b sql 脚本,可能同事 b 的代码运行就会报错,而且随着时间的推移,sql 脚本越来越多,项目上线的时候整理起来就很…

2026/10/10 6:01:46 阅读更多 →

最新新闻

Java集合框架底层原理与性能优化:从ArrayList到HashMap

Java集合框架底层原理与性能优化:从ArrayList到HashMap

Java里的集合框架,很多开发者从学习第一天就开始用,ArrayList存数据、HashMap做缓存,写着写着就成了肌肉记忆。但真正问你几个问题——ArrayList扩容到底怎么扩的?HashMap在JDK 8里引入红黑树是为什么?遍历的时候删元素…

2026/10/10 6:35:57 阅读更多 →
USACO白银组真题解析:BFS、序列DP与边界陷阱

USACO白银组真题解析:BFS、序列DP与边界陷阱

2008年2月的USACO白银组,是很多早期选手在 usaco.org 或 POJ 上反复刷的一套题。那年白银组一共三道题:Meteor Shower(流星雨)、Eating Together(一起吃饭)、Dining Cows(奶牛排队)&…

2026/10/10 6:35:57 阅读更多 →
微软DCU英国法庭首战:从技术溯源到司法打击黑产链

微软DCU英国法庭首战:从技术溯源到司法打击黑产链

事情得从微软数字犯罪部门第一次以原告身份走进英国法院说起。对圈外人来说,这可能只是一条关于“网络黑产被告上法庭”的新闻;但对做安全运营的人来说,这条消息传递的信号其实非常重:科技巨头开始从“受害者保护者”的位置&#…

2026/10/10 6:35:57 阅读更多 →
开发团队如何评估链助手内测分发的实际价值

开发团队如何评估链助手内测分发的实际价值

开发团队如何评估链助手内测分发的实际价值在移动应用快速迭代的背景下,许多技术负责人和开发者开始关注链助手内测分发服务的性价比怎么样。其核心价值并非单纯体现在价格层面,而在于如何在降低部署成本与提升测试效率之间找到最佳平衡点。链助手通过简…

2026/10/10 6:35:57 阅读更多 →
PCA9422+STM32电源管理:从分立LDO到可编程策略

PCA9422+STM32电源管理:从分立LDO到可编程策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 6:35:57 阅读更多 →
nano代替vim:Linux新手与运维的终端编辑新选择

nano代替vim:Linux新手与运维的终端编辑新选择

1. 别再被vim劝退了:聊聊nano代替vim这件事先说说我自己的经历。刚接触Linux那会儿,我听到最多的一句话就是“你必须学会vim,因为你迟早要在终端里改配置”。于是我硬着头皮看了不少教程,记住了i进入插入模式、Esc退出、:wq保存&a…

2026/10/10 6:34:57 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →