当前位置: 首页 > 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.lryc.cn/news/582123.html

相关文章:

  • 【AI大模型入门指南】机器学习入门详解
  • 烟雾,火焰探测器
  • Linux操作系统:软硬链接与动静态库
  • ClickHouse介绍与应用
  • 迁移GitLab,在新Linux中用Docker重新部署GitLab备份还原
  • C#中的BindingList有什么作用?
  • 【机器学习深度学习】多分类评估策略:混淆矩阵计算场景模拟示例
  • 亚马逊运营进阶指南:如何用AI工具赋能广告运营
  • 诊断工程师进阶篇 --- 车载诊断怎么与时俱进?
  • English Practice - Day 2
  • vite打包的简单配置
  • react状态管理库 - zustand
  • 风电自动化发电中的通信桥梁:CAN主站转MODBUS TCP网关解析
  • 【MyBatis】MyBatis与Spring和Spring Boot整合原理
  • 5种方法将联系人从iPhone转移到OnePlus
  • C++--map和set的使用
  • 仿mudou库one thread oneloop式并发服务器
  • 达梦数据库的信息查询
  • Redisson 分布式锁原理解析
  • Navicat Premium可视化工具使用查询控制台优化SQL语句
  • 商品中心—库存分桶高并发的优化文档
  • 力扣 3258 统计满足 K 约束的子字符串数量 I 题解
  • Java工具类,对象List提取某个属性为List,对象List转为对象Map其中某个属性作为Key值
  • RAG实战指南 Day 8:PDF、Word和HTML文档解析实战
  • UI自动化常见面试题
  • day08-Elasticsearch
  • 云计算领域“XaaS”是什么?
  • Python编译器(Pycharm Jupyter)
  • 第4.2节 Android App生成追溯关系
  • 【Mac 从 0 到 1 保姆级配置教程 19】- 英语学习篇-我的英语工作流分享(AI 辅助学习)