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: 是你最常用的写法。

相关推荐
今日无bug28 分钟前
大模型的回答是怎么一个字一个字蹦出来的?前端流式输出解析
前端·llm·agent
计算机魔术师29 分钟前
Anthropic 复盘 Claude 模型越权访问真实系统事件并改进对齐与安全措施
前端
谢白羽33 分钟前
SGLang的AWQ量化笔记
笔记·python·sglang
掘金者阿豪36 分钟前
Seedance 2.0/2.5 虚拟素材能跨 Key 共用吗?一次讲清 Asset ID、账号隔离与 SaaS 素材架构
前端·后端
用户50093768390391 小时前
热敏小票 Web 打印:我在 58/80mm 上折腾的两周
前端·vue.js
XGM1 小时前
一个前端新手的"顿悟"时刻:DOM树、渲染原理和JS动态渲染
前端
sakiko_1 小时前
OC基础语法(与Swift对比)-1
开发语言·ios·objective-c·swift
迷迭香yy1 小时前
基金档案数据工程实战从收入分析到持仓穿透的Python解析 IG50免费开源股票数据API接口
开发语言·python
东方小月1 小时前
一篇文章带你深入拆解Skill的本质与工程实现,让你不再滥用Skill
前端·人工智能·后端
清水白石0081 小时前
Python 类型设计深度解析:TypedDict 能否替代 dataclass?从 JSON 数据边界到 API 设计的最佳实践
java·python·json