当前位置: 首页 > news >正文

Day07- 管理并发和并行挑战:竞争条件和死锁

管理并发和并行挑战:竞争条件和死锁

并发和并行在 Python 中提供了显著的性能优势,但它们也引入了诸如竞态条件和死锁等挑战。理解和缓解这些问题对于构建健壮可靠的并发应用程序至关重要。本课程将深入探讨这些挑战,为您提供管理和有效应对它们的知识和工具。

理解竞态条件

当多个线程或进程并发访问和修改共享数据时,如果最终结果取决于这些访问发生的不可预测顺序,就会发生竞态条件。这可能导致意外和错误的结果。

竞态条件是如何发生的

想象有两个线程尝试递增一个共享的计数器变量。每个线程执行以下步骤:

  1. 读取计数器的当前值。
  2. 增加值。
  3. 将新值写回计数器。

如果两个线程同时执行这些步骤,可能会发生以下情况:

  1. 线程1读取计数器的值(例如,5)。
  2. 线程2读取计数器的值(例如,5)。
  3. 线程1将值增加到6。
  4. 线程2将值增加到6。
  5. 线程1将值6写回计数器。
  6. 线程2将值6写回计数器。

计数器没有增加到7(即被增加两次),而是只增加到6,因为两个线程读取了相同的初始值,然后互相覆盖了对方的更新。

未受保护的计数器

import threadingcounter = 0
num_increments = 100000def increment_counter():global counterfor _ in range(num_increments):counter += 1# 创建两个线程
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=increment_counter)#启动线程
thread1.start()
thread2.start()# 等待线程完成
thread1.join()
thread2.join()print(f"Final counter value: {counter}") # 预期:200000,但可能更少

在这个例子中,increment_counter 函数由两个线程执行。由于竞态条件,最终的计数器值可能会小于预期的 200000。

使用锁防止竞态条件

锁(也称为互斥锁)是一种同步机制,它允许同一时间只有一个线程访问共享资源。通过使用锁,我们可以保护访问和修改共享数据的代码的关键部分。

import threadingcounter = 0
num_increments = 100000
lock = threading.Lock() # 创建锁def increment_counter():global counterfor _ in range(num_increments):with lock: # 访问共享资源前获取锁counter += 1 # 临界区# 创建两个线程
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=increment_counter)#启动线程
thread1.start()
thread2.start()# 等待线程完成
thread1.join()
thread2.join()print(f"Final counter value: {counter}") # Expected: 200000

在这个修正的示例中,with lock: 语句确保同一时间只有一个线程可以执行 counter += 1 这行代码。当进入 with 块时锁会自动获取,并在退出时释放,即使发生异常也是如此。这防止了竞态条件,并确保计数器正确地递增。

现实中的竞态条件示例

  1. 银行系统: 想象两个并发事务尝试更新同一个银行账户余额。如果没有适当的同步,一个事务可能会覆盖另一个事务,导致余额不正确。
  2. 库存管理: 在一个电子商务系统中,多个用户可能会同时尝试购买库存中的最后一件商品。这可能导致商品被卖给的用户数量超过实际库存量。

假设情景

考虑一个管理共享缓存的多线程应用程序。多个线程可能会同时尝试更新或使缓存条目失效。如果没有适当的锁定机制,缓存可能会变得不一致,导致向用户提供错误的数据。

理解死锁

当两个或更多线程或进程无限期地相互等待释放它们所需的资源时,就会发生死锁。这会导致停滞状态,没有任何进展。

死锁条件

死锁通常在以下四个条件同时满足时发生,这些条件被称为 Coffman 条件:

  1. 互斥: 资源不可共享,意味着同一时间只有一个线程可以持有该资源。
  2. 持有等待: 线程在等待获取另一个资源时持有资源。
  3. 不可抢占: 资源不能被强制从持有它的线程中移除;它们必须被自愿释放。
  4. 循环等待: 两个或多个线程以循环方式互相等待(例如,线程 A 等待线程 B,线程 B 等待线程 A)。

示例:死锁场景

import threading
import timelock_a = threading.Lock()
lock_b = threading.Lock()def thread_one():lock_a.acquire()print("Thread one acquired lock A")time.sleep(0.1) # 模拟一些工作lock_b.acquire()print("Thread one acquired lock B")lock_b.release()lock_a.release()def thread_two():lock_b.acquire()print("Thread two acquired lock B")time.sleep(0.1) # 模拟一些工作lock_a.acquire()print("Thread two acquired lock A")lock_a.release()lock_b.release()# 创建并启动线程
thread1 = threading.Thread(target=thread_one)
thread2 = threading.Thread(target=thread_two)thread1.start()
thread2.start()thread1.join()
thread2.join()print("Finished")

在这个例子中,thread_one 获取了 lock_a,然后尝试获取 lock_b,而 thread_two 获取了 lock_b,然后尝试获取 lock_a。如果两个线程在另一个线程之前获取了它们第一个锁,它们都会无限期地被阻塞,等待另一个线程释放它们需要的锁,从而导致死锁。

防止死锁

可以使用多种策略来防止死锁:

  1. 锁顺序: 建立一致的锁获取顺序。如果所有线程都以相同的顺序获取锁,就可以防止循环等待条件。
  2. 超时: 在获取锁时使用超时。如果线程在特定时间内无法获取锁,它会释放当前持有的所有锁并稍后重试。这可以防止无限期等待。
  3. 资源层级: 为资源分配层级,并要求线程按层级升序获取资源。
  4. 死锁检测与恢复: 检测死锁并采取行动打破它们,例如通过中止其中一个死锁的线程。

示例:通过锁排序防止死锁

import threading
import timelock_a = threading.Lock()
lock_b = threading.Lock()def thread_one():# 按一致的顺序获取锁(先 A 后 B)lock_a.acquire()print("Thread one acquired lock A")time.sleep(0.1)lock_b.acquire()print("Thread one acquired lock B")lock_b.release()lock_a.release()def thread_two():# 按相同顺序获取锁(先 A 后 B)lock_a.acquire() #改为先获取lock_aprint("Thread two acquired lock A")time.sleep(0.1)lock_b.acquire()print("Thread two acquired lock B")lock_b.release()lock_a.release()# 创建并启动线程
thread1 = threading.Thread(target=thread_one)
thread2 = threading.Thread(target=thread_two)thread1.start()
thread2.start()thread1.join()
thread2.join()print("Finished")

在这个修正的例子中,两个线程在获取 lock_a 之前先获取 lock_b。这消除了循环等待条件,并防止了死锁。

现实中的死锁实例

  1. 操作系统: 当多个进程竞争内存、文件和 I/O 设备等资源时,操作系统可能会发生死锁。
  2. 数据库系统: 当多个事务互相等待释放数据库行或表上的锁时,数据库系统可能会发生死锁。
http://www.dtcms.com/a/268797.html

相关文章:

  • 在bash shell 函数传递数组的问题2
  • 【DeepSeek实战】17、MCP地图服务集成全景指南:高德、百度、腾讯三大平台接入实战
  • PCIE Ack/Nak机制详解
  • Unity 实现与 Ollama API 交互的实时流式响应处理
  • ES 压缩包安装
  • socket接口api的深度探究
  • 初识Neo4j之Cypher
  • 【Unity笔记】Unity 粒子系统 Triggers 使用解析:监听粒子进入与离开区域并触发事件
  • 在 macOS 上安装和测试 LibreOffice
  • 深入解析TCP:可靠传输的核心机制与实现逻辑(三次握手、四次挥手、流量控制、滑动窗口、拥塞控制、慢启动、延时应答、面向字节流、粘包问题)
  • 借助HarmonyOS SDK,《NBA巅峰对决》实现“分钟级启动”到“秒级进场”
  • 【7】PostgreSQL 事务
  • SRAM与三级缓存(L1/L2/L3 Cache)的关系
  • 芯谷科技--高性能双运算放大器D358
  • 第二届云计算与大数据国际学术会议(ICCBD 2025)
  • 火山引擎Data Agent全面上线售卖!以企业级数据智能体,重构数据应用范式
  • PostgreSQL中的HASH分区:原理、实现与最佳实践
  • 查看WPS Ofice是64位还是32位
  • 腾讯云 CDN 不支持 WebSocket 的现状与华为云 CDN 的替代方案-优雅草卓伊凡
  • 缺乏项目进度追踪工具,如何选择适合的工具
  • 中电金信 :十问高质量数据集:金融大模型价值重塑有“据”可循
  • 案例分享:应用VIC-3D High-Speed FFT进行吉他拨弦振动的工作变形ODS测量
  • QML中的Item
  • 【银行测试】手机银行APP专项项目+测试点汇总(二)
  • RESTful API概念和设计原则
  • C++之string类的实现代码及其详解(中)
  • 软件之禅(十二)面向对象和市场经济---平等性原理
  • 对象存储-OSS
  • PC端基于SpringBoot架构控制无人机(三):系统架构设计
  • Vite 常用配置详解