在 Python 多线程编程中,锁是保证共享资源安全访问的核心机制。然而,锁的不当使用往往会引发新的问题,如死锁、性能损耗等。本文结合实际项目场景,深入剖析锁在使用过程中的常见问题,并提供可落地的解决方法。
一、死锁:线程间的无限等待
1.1 交叉锁竞争导致死锁
问题表现
当两个或多个线程相互持有对方需要的锁,并无限期等待时,就会发生死锁。例如在电商订单处理场景中,线程 A 持有订单锁并等待库存锁,线程 B 持有库存锁并等待订单锁,双方陷入永久阻塞。
import threading
import time
order_lock = threading.Lock()
inventory_lock = threading.Lock()
def process_order():
with order_lock:
print("线程A获取订单锁,等待库存锁")
time.sleep(1) # 模拟订单处理耗时
with inventory_lock:
print("线程A获取库存锁,处理订单")
def update_inventory():
with inventory_lock:
print("线程B获取库存锁,等待订单锁")
time.sleep(1) # 模拟库存查询耗时
with order_lock:
print("线程B获取订单锁,更新库存")
t1 = threading.Thread(target=process_order)
t2 = threading.Thread(target=update_inventory)
t1.start()
t2.start()
解决方法:统一锁获取顺序
所有线程严格按照预设的顺序获取锁,消除交叉等待条件。例如规定必须先获取订单锁,再获取库存锁。
def process_order_safe():
with order_lock:
print("线程A获取订单锁")
time.sleep(1)
with inventory_lock:
print("线程A获取库存锁,处理订单")
def update_inventory_safe():
with order_lock: # 先获取订单锁,再获取库存锁
print("线程B获取订单锁")
time.sleep(1)
with inventory_lock:
print("线程B获取库存锁,更新库存")
1.2 递归锁使用不当引发死锁
问题表现
在递归调用场景中,使用普通互斥锁(Lock)会导致同一线程重复获取锁时发生死锁。例如在树形结构遍历中,递归函数需要多次访问共享资源。
data_lock = threading.Lock()
def recursive_traverse(node):
with data_lock: # 第一次获取成功
print(f"处理节点 {node}")
if node.children:
recursive_traverse(node.children[0]