Python 多线程:threading 模块与锁机制
- 打开 代码编辑器或集成开发环境,新建 空白文件并 保存 为
threading_lock.py。 - 引入 标准库模块。在文件首行 输入
import threading与import time。threading提供线程控制接口,time用于人为制造耗时以放大并发冲突窗口。
- 声明 共享内存结构。在模块层级 定义 字典变量
shared_data = {"balance": 0}。该对象将作为多个线程并发读写的全局资源。 - 编写 无保护的工作函数。输入 以下逻辑:
def unsafe_worker(): for _ in range(1000): temp_val = shared_data["balance"] time.sleep(0.0001) # 模拟 I/O 延迟 shared_data["balance"] = temp_val + 1此函数执行“读取-暂停-写入”流程。暂停操作会释放 Python 的 GIL(全局解释器锁),允许操作系统调度其他线程切入。当多个线程同时读到旧值并分别写入时,后写入的结果会直接覆盖前者的增量,导致数据丢失。
- 实例化 执行单元。调用
threading.Thread(target=unsafe_worker)两次,分别 赋值 给t1与t2。 - 触发 并行调度。依次 执行
t1.start()与t2.start()。两个独立执行栈将交错运行。 - 等待 任务收尾。依次 调用
t1.join()与t2.join()。主程序将阻塞在此处,直至子线程内部循环完全退出。 - 核对 最终状态。打印
shared_data["balance"]。观察 终端输出,确认数值必然低于理论值2000。此步骤验证竞态条件已破坏数据完整性。
- 创建 互斥锁对象。在函数外部 编写
data_lock = threading.Lock()。Lock基于底层信号量实现,提供排他性访问权限,确保同一时间仅有一个线程进入临界区(即修改共享数据的代码段)。 - 隔离 关键逻辑。重构
unsafe_worker函数体,插入 锁控制代码:def safe_worker_manual(): for _ in range(1000): data_lock.acquire() try: temp_val = shared_data["balance"] time.sleep(0.0001) shared_data["balance"] = temp_val + 1 finally: data_lock.release()acquire()尝试获取锁,若锁被占用则强制当前线程挂起。try...finally结构保证即使循环内部抛出异常,release()也会被强制执行,防止锁资源永久泄漏。 - 验证 原子性修复。替换 线程目标的函数名为
safe_worker_manual,运行 脚本。确认 终端输出严格等于2000,证明并发写入已被串行化。
- 评估 手动管理成本。显式调用
acquire与release容易因缩进偏移或异常分支未覆盖导致死锁。 - 切换 上下文管理器。使用
with语句简化函数实现:def safe_worker_context(): for _ in range(1000): with data_lock: temp_val = shared_data["balance"] time.sleep(0.0001) shared_data["balance"] = temp_val + 1with data_lock:在进入缩进块时自动执行acquire(),在退出块时(无论正常结束还是异常中断)自动执行release()。 - 对比 锁控制方案。参考下表执行技术选型:
| 管理方式 | 代码侵入度 | 异常防护能力 | 推荐应用场景 |
|---|---|---|---|
acquire() / release() |
高(需显式配对与异常捕获) | 强依赖 try...finally 结构编写质量 |
需精确设置超时阈值或配合条件变量 |
with 上下文语句 |
极低(声明式自动接管) | 底层封装完整释放逻辑,天然防泄漏 | 常规业务逻辑保护与日常工程开发 |
- 配置 阻塞超时参数。在易发生长等待的场景中,替换 获取动作为
data_lock.acquire(timeout=2)。线程最多等待2秒,超时后方法返回False。业务代码可根据布尔值 执行 降级处理或重试逻辑,避免主流程永久僵死。
- 实例化 可重入锁。声明
rlock = threading.RLock()。标准Lock一旦被持有线程再次申请将直接阻塞自身,引发死锁。RLock内部维护递归计数器,允许同一线程多次嵌套进入临界区。 - 模拟 递归调用场景。编写外层函数获取锁后,调用 内部函数并再次执行
rlock.acquire()。RLock计数器自增,逻辑平滑向下执行。 - 配对 释放次数。严格遵循“获取几次,释放几次”原则。当计数器回归
0时,底层排他限制才真正解除,其他线程方可竞争。 - 封装 安全类结构。在面向对象架构中,将锁对象定义为实例私有属性
self._lock = threading.RLock()。在修改实例状态的每个公共方法首行 添加with self._lock:。此设计将并发安全内聚至对象层级,隔离外部线程干扰。

暂无评论,快来抢沙发吧!