【多线程】竞态条件是什么?
【多线程】竞态条件是什么?
本文来自于我关于多线程系列文章。欢迎阅读、点评与交流
1.【多线程】互斥锁(Mutex)是什么?
2.【多线程】临界区(Critical Section)是什么?
3.【多线程】计算机领域中的各种锁
4.【多线程】信号量(Semaphore)是什么?
5.【多线程】信号量(Semaphore)常见的应用场景
6.【多线程】条件变量(Condition Variable)是什么?
7.【多线程】监视器(Monitor)是什么?
8.【多线程】什么是原子操作(Atomic Operation)?
9.【多线程】竞态条件是什么?
竞态条件 是指一个系统或进程的输出,依赖于不受控制的事件发生的顺序或时机。当多个线程/进程同时访问和操作共享数据时,最终的执行结果取决于它们执行的精确顺序,而这个顺序是无法预测的,从而导致不可预知的行为。
一个生动的比喻:银行转账
假设你的银行账户里有 100元。你同时做了两件事:
- 操作A(存款):在你的手机APP上,点击存入 50元。
- 操作B(取款):在ATM上,点击取出 50元。
这两个操作几乎在同一时刻发生,相当于两个“线程”在同时处理你的账户余额。
正常的、正确的流程应该是(互斥的):
· 线程A:读取余额(100) → 计算新余额(100 + 50 = 150) → 写入新余额(150)
· 线程B:读取余额(150) → 计算新余额(150 - 50 = 100) → 写入新余额(100)
· 最终结果: 余额为 100元,正确。
但在竞态条件下,可能会发生这样的错误流程:
- 时刻1:线程A读取余额,得到 100。
- 时刻2:在线程A计算和写入新余额之前,线程B也读取了余额,它读到的也是 100。
- 时刻3:线程A计算 100 + 50 = 150,并写入新余额 150。
- 时刻4:线程B计算 100 - 50 = 50,并写入新余额 50。
- 最终结果:你的账户余额变成了 50元。你存的钱“消失”了!
问题出在哪?
出在“读取-计算-写入”这一系列操作不是原子操作(不可分割的整体)。两个线程的执行顺序交织在了一起,导致了错误的结果。这个“竞争”的就是谁先读取和谁先写入。
竞态条件产生的必要条件
- 存在共享资源(临界资源):比如上面的银行账户余额。
- 多个线程同时对该资源进行读写操作:如果都是只读操作,不会产生问题。
- 缺少同步机制:没有对共享资源的访问进行保护,使得操作不是原子的。
在编程中的实际例子
import threading# 共享资源
counter = 0def increment():global counterfor _ in range(100000):# 1. 读取counter的值到寄存器# 2. 将寄存器中的值+1# 3. 将新值写回countercounter += 1# 创建两个线程
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)# 启动线程
t1.start()
t2.start()# 等待两个线程执行完毕
t1.join()
t2.join()# 预期结果:counter = 200000
print(f"Final counter value: {counter}")
运行这段代码,你很可能得不到预期的 200,000。因为 counter += 1 这个操作在底层并不是一步完成的,两个线程会互相覆盖对方的结果。
如何解决竞态条件?
解决竞态条件的核心思想是:将“读写共享资源”的关键代码段变成一个原子操作,即在同一时刻,只有一个线程可以执行这段代码。
常用的同步机制有:
- 互斥锁
这是最常用的方法。线程在进入临界区(访问共享资源的代码)前先获取锁,如果锁已被其他线程占用,则等待;退出临界区时释放锁。
现在,每次只有一个线程能执行 counter += 1,结果必然是 200,000。import threadingcounter = 0 lock = threading.Lock() # 创建一个锁def increment():global counterfor _ in range(100000):lock.acquire() # 获取锁try:counter += 1 # 临界区finally:lock.release() # 释放锁# ... 其余代码相同
- 信号量
- 监视器
- 使用线程安全的数据结构
总结
特性 描述
本质 结果依赖于不受控制的执行顺序。
发生场景 多线程/多进程并发读写共享资源时。
危害 导致数据不一致、程序行为不可预测、难以复现和调试的Bug。
解决方案 使用同步机制(如锁)来保证临界区的互斥访问。
简单来说,竞态条件就是一个因为“抢”资源而引发的混乱状态,而解决之道就是建立“排队”或“独占”的规则(锁)。