Python线程锁:为什么多线程会“打架“,以及怎么解决

先看一个问题:不加锁会怎样?

假设你有一个银行账户,余额1000元。两个线程同时取钱:

python 复制代码
import threading

balance = 1000

def withdraw(amount):
    global balance
    temp = balance          # 读余额
    temp -= amount          # 减钱
    balance = temp          # 写回余额
    print(f"取出 {amount}, 剩余 {balance}")

t1 = threading.Thread(target=withdraw, args=(800,))
t2 = threading.Thread(target=withdraw, args=(800,))

t1.start()
t2.start()
t1.join()
t2.join()

print(f"最终余额: {balance}")

你觉得最终余额是多少?

答案:可能是 -600,也可能是 200,也可能是 400。

因为两个线程同时在读写 balance,互相覆盖了对方的结果。这就是竞态条件(Race Condition)


用一张图理解发生了什么

时间 线程1 线程2 balance
T1 读 balance = 1000 1000
T2 读 balance = 1000 1000
T3 减800 → temp = 200 1000
T4 减800 → temp = 200 1000
T5 写回 balance = 200 200
T6 写回 balance = 200 200

两个线程各取了800,总共取了1600,但余额只少了800。钱"丢"了。


线程锁是什么?

一句话:锁就是一把钥匙,同一时间只有一个线程能拿到钥匙,进门干活。

python 复制代码
import threading

balance = 1000
lock = threading.Lock()  # 创建一把锁

def withdraw(amount):
    global balance
    lock.acquire()         # 🔑 拿钥匙,进门
    try:
        temp = balance
        temp -= amount
        balance = temp
        print(f"取出 {amount}, 剩余 {balance}")
    finally:
        lock.release()     # 🔓 还钥匙,出门

t1 = threading.Thread(target=withdraw, args=(800,))
t2 = threading.Thread(target=withdraw, args=(800,))

t1.start()
t2.start()
t1.join()
t2.join()

print(f"最终余额: {balance}")

输出:

复制代码
取出 800, 剩余 200
取出 800, 剩余 -600
最终余额: -600

✅ 结果正确了。虽然余额是负数(业务逻辑问题),但两次取钱都被正确记录了,没有互相覆盖。


更推荐的写法:with 语句

手动 acquire/release 容易忘,用 with 自动管理:

python 复制代码
def withdraw(amount):
    global balance
    with lock:              # 自动拿钥匙 + 自动还钥匙
        temp = balance
        temp -= amount
        balance = temp

推荐所有场景都用 with lock:,不会忘还钥匙。


锁的三个核心问题

问题 说明 解决方案
什么时候加锁? 只要多个线程会同时读写同一个变量,就要加锁 共享数据必加锁
锁的范围多大? 锁住的代码越少越好,只锁读写那几行 不要把整个函数都锁住
忘记释放锁? 线程崩溃,锁没还,其他线程永远等着(死锁) with lock: 自动释放

实战:你之前的响应时间列表

回到你之前的场景:

python 复制代码
from collections import deque
import threading

response_times = deque(maxlen=100)
lock = threading.Lock()

def add_response_time(t):
    with lock:              # 加锁保护
        response_times.append(t)

def get_average():
    with lock:
        if len(response_times) == 0:
            return 0
        return sum(response_times) / len(response_times)

✅ 任何时候操作 response_times,都先拿锁,避免多线程同时修改导致数据错乱。


什么时候不需要锁?

场景 是否需要锁 原因
多个线程只读,不写 ❌ 不需要 读不会互相影响
每个线程操作自己的变量 ❌ 不需要 没有共享数据
多个线程读写同一个变量 必须加锁 会互相覆盖

一句话总结

线程锁 = 排队机制。多个线程抢同一个资源时,锁保证同一时间只有一个线程能操作,防止数据被互相踩踏。

记住:with lock: 是你最常用的写法。

相关推荐
guygg881 小时前
人行走作用下板的振动响应 MATLAB 仿真
开发语言·matlab
小白学大数据1 小时前
线上故障急救:依托 OpenClaw 日志排查 403 和 503 问题
爬虫·python·selenium·数据分析
海兰2 小时前
【web应用】Excel 项目数据自动化分析系统(AI 驱动分析)详细设计与部署指南(附源代码)
前端·人工智能·自动化·excel
小二·2 小时前
Next.js 15 全栈开发实战
开发语言·javascript·ecmascript
2501_940041742 小时前
技术分享:高质量全栈开发提示词设计实践 —— 覆盖简单到复杂
前端·prompt
fox_lht2 小时前
15.3.改进我们之前的输入、输出项目
开发语言·后端·学习·rust
databook2 小时前
用SymPy自动因式分解:从面积拼图到代数恒等式
python·数学·动效
IT_陈寒2 小时前
Python的os.path.join居然能这么坑?
前端·人工智能·后端
java1234_小锋2 小时前
LangChain4j 开发Java Agent智能体- 多模态支持
java·开发语言·langchain4j