Python threading中如何正确处理竞态条件且不影响程序运行性能
问题根源梳理
你当前遇到的竞态问题和锁失效的核心原因有三个:
- 错误共享实例可变属性:
run方法循环中反复修改self.user_id、self.amount两个实例属性,如果makePurchase内直接读取实例属性而非传入参数,就会出现值还没被读取就被下一轮循环覆盖的情况,锁放在方法内部根本保护不了外部的赋值逻辑 - 锁的作用域错误:当前用的是
Connection实例级别的锁,如果多个Connection实例操作同一个用户的数据,锁完全不共享,无法避免跨实例的竞态 - 无限制创建线程:每个用户购买都开新线程,用户量大时线程上下文切换开销会远大于任务本身的执行开销,反而拉低性能
解决方案
1. 消除实例属性的共享误用
不要用实例属性传递单次循环的用户数据,直接传循环变量的当前值,同时makePurchase内全部使用传入的参数,不碰实例的全局可变属性:
def run( self ): self.users_list = self.getUsers( self.symbol ) if self.users_list["count"] > 0: for u in self.users_list["data"]: # 直接用局部变量传参,不要修改self的属性 user_id = u["user_id"] amount = u["amount"] t = Thread(target=self.makePurchase, args=(self.symbol, user_id, amount, )) t.start()
2. 改用用户级细粒度锁,兼顾安全和性能
使用全局用户锁字典,只让同一个用户的操作串行,不同用户的操作完全并行,不会影响整体性能:
# Connect.py 顶部新增全局锁管理逻辑 from threading import Thread, Lock # 全局用户锁字典,key为user_id,value为对应锁对象 user_locks = {} # 保护锁字典本身的线程安全 lock_of_locks = Lock() def get_user_lock(user_id): with lock_of_locks: if user_id not in user_locks: user_locks[user_id] = Lock() return user_locks[user_id]
修改makePurchase的加锁逻辑,用with自动管理锁释放,避免异常导致死锁:
def makePurchase( self, symbol, user_id, amount ): lock = get_user_lock(user_id) with lock: # 此处写购买逻辑,同一用户的操作串行,不同用户完全并行,无额外性能损耗
3. 用线程池限制并发,避免资源耗尽
替换无限制创建线程的逻辑,用线程池控制最大并发数,降低系统调度开销:
from concurrent.futures import ThreadPoolExecutor # 可根据服务器性能调整最大并发数,推荐设置为CPU核心数的2-4倍 MAX_WORKERS = 20 class Connection( threading.Thread ): # 其他逻辑不变 def run( self ): self.users_list = self.getUsers( self.symbol ) if self.users_list["count"] > 0: with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: for u in self.users_list["data"]: user_id = u["user_id"] amount = u["amount"] executor.submit(self.makePurchase, self.symbol, user_id, amount)
线程池会自动管理线程生命周期,不需要手动join,也不会出现线程数过多导致的性能下降问题。
4. 可选数据库层面兜底
如果购买逻辑涉及数据库修改用户余额,可在数据库层面加行锁/乐观锁做双重保障,示例SQL:
UPDATE user_account SET balance = balance - %s WHERE user_id = %s AND balance >= %s
内容的提问来源于stack exchange,提问作者EricW
相关产品推荐
相关产品推荐

