文章目录

Python 多线程:threading 模块与锁机制

发布于 2026-04-07 00:12:26 · 浏览 16 次 · 评论 0 条

Python 多线程:threading 模块与锁机制


  1. 打开 代码编辑器或集成开发环境,新建 空白文件并 保存threading_lock.py
  2. 引入 标准库模块。在文件首行 输入 import threadingimport timethreading 提供线程控制接口,time 用于人为制造耗时以放大并发冲突窗口。

  1. 声明 共享内存结构。在模块层级 定义 字典变量 shared_data = {"balance": 0}。该对象将作为多个线程并发读写的全局资源。
  2. 编写 无保护的工作函数。输入 以下逻辑:
    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(全局解释器锁),允许操作系统调度其他线程切入。当多个线程同时读到旧值并分别写入时,后写入的结果会直接覆盖前者的增量,导致数据丢失。

  3. 实例化 执行单元。调用 threading.Thread(target=unsafe_worker) 两次,分别 赋值t1t2
  4. 触发 并行调度。依次 执行 t1.start()t2.start()。两个独立执行栈将交错运行。
  5. 等待 任务收尾。依次 调用 t1.join()t2.join()。主程序将阻塞在此处,直至子线程内部循环完全退出。
  6. 核对 最终状态。打印 shared_data["balance"]观察 终端输出,确认数值必然低于理论值 2000。此步骤验证竞态条件已破坏数据完整性。

  1. 创建 互斥锁对象。在函数外部 编写 data_lock = threading.Lock()Lock 基于底层信号量实现,提供排他性访问权限,确保同一时间仅有一个线程进入临界区(即修改共享数据的代码段)。
  2. 隔离 关键逻辑。重构 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() 也会被强制执行,防止锁资源永久泄漏。

  3. 验证 原子性修复。替换 线程目标的函数名为 safe_worker_manual运行 脚本。确认 终端输出严格等于 2000,证明并发写入已被串行化。

  1. 评估 手动管理成本。显式调用 acquirerelease 容易因缩进偏移或异常分支未覆盖导致死锁。
  2. 切换 上下文管理器。使用 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 + 1

    with data_lock: 在进入缩进块时自动执行 acquire(),在退出块时(无论正常结束还是异常中断)自动执行 release()

  3. 对比 锁控制方案。参考下表执行技术选型:
管理方式 代码侵入度 异常防护能力 推荐应用场景
acquire() / release() 高(需显式配对与异常捕获) 强依赖 try...finally 结构编写质量 需精确设置超时阈值或配合条件变量
with 上下文语句 极低(声明式自动接管) 底层封装完整释放逻辑,天然防泄漏 常规业务逻辑保护与日常工程开发
  1. 配置 阻塞超时参数。在易发生长等待的场景中,替换 获取动作为 data_lock.acquire(timeout=2)。线程最多等待 2 秒,超时后方法返回 False。业务代码可根据布尔值 执行 降级处理或重试逻辑,避免主流程永久僵死。

  1. 实例化 可重入锁。声明 rlock = threading.RLock()。标准 Lock 一旦被持有线程再次申请将直接阻塞自身,引发死锁。RLock 内部维护递归计数器,允许同一线程多次嵌套进入临界区。
  2. 模拟 递归调用场景。编写外层函数获取锁后,调用 内部函数并再次执行 rlock.acquire()RLock 计数器自增,逻辑平滑向下执行。
  3. 配对 释放次数。严格遵循“获取几次,释放几次”原则。当计数器回归 0 时,底层排他限制才真正解除,其他线程方可竞争。
  4. 封装 安全类结构。在面向对象架构中,将锁对象定义为实例私有属性 self._lock = threading.RLock()。在修改实例状态的每个公共方法首行 添加 with self._lock:。此设计将并发安全内聚至对象层级,隔离外部线程干扰。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文