import threading
import time
# 共享数据
user_transactions = {}
lock = threading.Lock()
# 添加交易记录的线程函数
def add_transaction(user_id, transaction):
global user_transactions
while True:
with lock:
if user_id not in user_transactions:
user_transactions[user_id] = []
user_transactions[user_id].append(transaction)
time.sleep(1)
# 统计交易记录总和的线程函数
def sum_transactions(user_id):
global user_transactions
while True:
with lock:
if user_id in user_transactions:
total = sum(user_transactions[user_id])
print(f"User {user_id}'s total transaction: {total}")
time.sleep(2)
# 创建并启动线程
add_thread = threading.Thread(target=add_transaction, args=(1, 10))
sum_thread = threading.Thread(target=sum_transactions, args=(1,))
add_thread.start()
sum_thread.start()
# 等待线程结束
add_thread.join()
sum_thread.join()
线程间资源竞争问题处理
- 使用锁机制:在上述代码中,我们使用了
threading.Lock()
创建了一个互斥锁 lock
。在访问和修改共享数据 user_transactions
之前,使用 with lock:
语句获取锁,这能确保同一时间只有一个线程可以操作共享数据,从而避免资源竞争。
不同锁机制在该场景中的应用
- 互斥锁(Mutex):
- 原理:互斥锁只有两种状态,锁定和未锁定。当一个线程获取了互斥锁,其他线程就无法获取,直到该线程释放锁。
- 应用场景:在我们的例子中非常适用,因为共享数据
user_transactions
是一个全局字典,同一时间只允许一个线程进行读写操作,防止数据不一致。例如,如果两个线程同时尝试添加交易记录,可能导致数据覆盖或字典结构损坏。使用互斥锁就能避免这种情况。
- 信号量(Semaphore):
- 原理:信号量维护了一个内部计数器,当线程获取信号量时,计数器减1,当线程释放信号量时,计数器加1。当计数器为0时,其他线程无法获取信号量。
- 应用场景:如果我们希望控制同时访问共享数据的线程数量,可以使用信号量。例如,如果我们允许最多3个线程同时读取
user_transactions
字典进行求和统计,就可以创建一个初始值为3的信号量。这样既能提高并发效率,又能在一定程度上保证数据的一致性。不过在本题场景下,由于写操作需要严格的独占性,使用互斥锁更为直接和合适,信号量相对更适合读多写少且对并发读有一定控制需求的场景。