Lock 是 Python 中用于线程同步的基本机制,它提供了最基本的互斥锁功能,确保多个线程不会同时访问共享资源。
基本概念
什么是锁?
锁是一种同步原语,用于保护共享资源,防止多个线程同时修改数据导致的数据不一致问题。
Lock 的创建和使用
1. 基本创建
import threading
# 创建锁对象
lock = threading.Lock()
2. 主要方法
acquire(blocking=True, timeout=-1)
- blocking: 是否阻塞等待锁
- timeout: 超时时间(秒)
- 返回值: 成功获取锁返回 True,否则返回 False
release()
- 释放锁
- 只有持有锁的线程才能释放锁
使用示例
示例1:基本使用
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
线程锁(threading.Lock)实现的线程安全计数器示例
这个程序演示了如何使用Python的threading.Lock来保护共享资源,
确保在多线程环境下对共享数据的操作是线程安全的。
"""
import threading # 导入threading模块,用于多线程编程
import time # 导入time模块,用于添加延时
class Counter:
"""
线程安全的计数器类
使用threading.Lock来保护共享资源(value),确保多线程环境下的数据一致性
"""
def __init__(self):
"""初始化计数器"""
self.value = 0 # 计数器初始值为0
self.lock = threading.Lock() # 创建线程锁对象
def increment(self):
"""
线程安全的递增方法
使用with语句自动管理锁的获取和释放,确保即使发生异常也能正确释放锁
"""
with self.lock: # 自动获取和释放锁,进入临界区
self.value += 1 # 对共享变量进行操作
print(f"Counter: {self.value}") # 打印当前计数值
def worker(counter, num_iterations):
"""
工作线程函数
Args:
counter: Counter对象,共享的计数器实例
num_iterations: int,要执行的递增操作次数
"""
for _ in range(num_iterations):
counter.increment() # 调用计数器的线程安全递增方法
time.sleep(0.01) # 添加短暂延时,增加线程切换的可能性,更好地展示并发效果
if __name__ == "__main__":
# 创建计数器实例(共享资源)
counter = Counter()
# 创建多个线程列表,用于后续管理
threads = []
# 创建5个工作线程,每个线程执行10次递增操作
for i in range(5):
t = threading.Thread(target=worker, args=(counter, 10))
threads.append(t) # 将线程添加到线程列表
t.start() # 启动线程
# 等待所有线程完成执行
for t in threads:
t.join() # 主线程阻塞,直到对应的子线程结束
# 打印最终的计数器值,应该等于线程数×每个线程的迭代次数 = 5×10 = 50
print(f"Final counter value: {counter.value}")
示例2:手动获取和释放锁
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
线程安全与不安全操作对比示例
这个程序演示了在多线程环境中使用锁(threading.Lock)保护共享资源的重要性,
通过对比安全操作和不安全操作的结果,展示线程锁如何防止数据竞争。
"""
import threading
import time
import random # 导入random模块用于添加随机延迟
import sys # 导入sys模块
# 定义共享数据
shared_data = 0
# 创建锁对象
lock = threading.Lock()
# 设置较小的输出缓冲区,以便实时看到输出
sys.stdout.reconfigure(line_buffering=True)
def safe_increment():
"""
使用锁保护的安全递增操作
通过try-finally确保即使发生异常也能释放锁,防止死锁
"""
global shared_data
lock.acquire() # 获取锁
try:
# 临界区代码 - 在锁的保护下操作共享资源
shared_data += 1
# 只在简单演示中打印详细信息,避免大量输出
if threading.current_thread().name.startswith("Simple"):
print(f"Data: {shared_data}")
finally:
lock.release() # 确保锁被释放
def unsafe_increment():
"""
没有锁保护的不安全递增操作
多线程同时访问时可能出现数据竞争,导致结果不正确
"""
global shared_data
# 没有锁保护,可能出现竞态条件
temp = shared_data # 读取当前值
# 添加一个小的延迟,增加线程切换的可能性,更容易观察到竞态条件
time.sleep(random.uniform(0, 0.001))
shared_data = temp + 1 # 写回递增后的值
# 只在简单演示中打印详细信息,避免大量输出
if threading.current_thread().name.startswith("Simple"):
print(f"Unsafe data: {shared_data}")
def demo_safe_operation(num_threads=50, iterations_per_thread=1000):
"""
演示安全操作的结果
Args:
num_threads: 线程数量
iterations_per_thread: 每个线程的迭代次数
"""
global shared_data
shared_data = 0 # 重置共享数据
print(f"\n=== 演示安全操作 (使用锁) ===")
print(f"初始化 shared_data = {shared_data}")
threads = []
start_time = time.time()
# 创建多个线程执行安全递增
for _ in range(num_threads):
t = threading.Thread(target=lambda: [safe_increment() for _ in range(iterations_per_thread)])
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
execution_time = time.time() - start_time
# 打印最终结果
expected = num_threads * iterations_per_thread
print(f"最终 shared_data = {shared_data}, 期望值 = {expected}")
print(f"结果{' 正确' if shared_data == expected else ' 错误'} (有锁保护)")
print(f"执行时间: {execution_time:.4f}秒")
def demo_unsafe_operation(num_threads=50, iterations_per_thread=1000):
"""
演示不安全操作的结果
Args:
num_threads: 线程数量
iterations_per_thread: 每个线程的迭代次数
"""
global shared_data
shared_data = 0 # 重置共享数据
print(f"\n=== 演示不安全操作 (无锁保护) ===")
print(f"初始化 shared_data = {shared_data}")
threads = []
start_time = time.time()
# 创建多个线程执行不安全递增
for _ in range(num_threads):
t = threading.Thread(target=lambda: [unsafe_increment() for _ in range(iterations_per_thread)])
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
execution_time = time.time() - start_time
# 打印最终结果
expected = num_threads * iterations_per_thread
print(f"最终 shared_data = {shared_data}, 期望值 = {expected}")
print(f"结果{' 正确' if shared_data == expected else ' 错误'} (无锁保护)")
print(f"丢失的更新次数: {expected - shared_data}") # 显示丢失的更新次数
print(f"执行时间: {execution_time:.4f}秒")
def simple_demo():
"""
简单演示 - 适用于快速观察锁的工作方式
每个函数只执行少量操作,便于观察输出
"""
global shared_data
# 简单演示安全操作
shared_data = 0
print("\n=== 简单安全操作演示 ===")
threads = []
for i in range(5):
t = threading.Thread(target=safe_increment, name=f"SimpleSafeThread-{i}")
threads.append(t)
t.start()
time.sleep(0.05) # 添加短暂延迟以便于观察
for t in threads:
t.join()
print(f"安全操作最终结果: {shared_data}")
# 简单演示不安全操作
shared_data = 0
print("\n=== 简单不安全操作演示 ===")
threads = []
for i in range(5):
t = threading.Thread(target=unsafe_increment, name=f"SimpleUnsafeThread-{i}")
threads.append(t)
t.start()
time.sleep(0.05) # 添加短暂延迟以便于观察
for t in threads:
t.join()
print(f"不安全操作最终结果: {shared_data}")
def enhance_demo():
"""
增强演示 - 专门设计用来展示竞态条件
使用多个线程对同一变量执行读取-修改-写入操作
"""
global shared_data
print("\n=== 增强竞态条件演示 ===")
print("这个演示特意设计来展示线程不安全操作中的竞态条件问题")
# 重置共享数据
shared_data = 0
expected = 5000 # 50个线程,每个线程100次操作
# 创建多个线程执行不安全递增操作
threads = []
for _ in range(50):
t = threading.Thread(target=lambda: [unsafe_increment() for _ in range(100)])
threads.append(t)
# 同时启动所有线程,增加竞争的可能性
print(f"启动{len(threads)}个线程,每个线程执行100次不安全递增操作...")
for t in threads:
t.start()
# 等待所有线程完成
for t in threads:
t.join()
# 显示结果并分析问题
print(f"\n增强演示结果:")
print(f"期望结果: {expected}")
print(f"实际结果: {shared_data}")
print(f"差值: {expected - shared_data} (丢失了{expected - shared_data}次更新)")
if shared_data < expected:
print("\n这清楚地展示了竞态条件的问题!多个线程同时读取同一个值,")
print("递增后又同时写回,导致某些递增操作被覆盖,最终结果小于期望值。")
print("在实际应用中,这种问题可能导致数据不一致、财务计算错误等严重后果。")
else:
print("\n注意:在某些环境下,即使没有锁保护,简单的整数递增操作")
print("也可能因为Python的GIL(全局解释器锁)而表现出看似正确的行为。")
print("但这不意味着我们可以不使用锁,对于更复杂的操作或在其他语言中,")
print("竞态条件的问题更加明显。")
if __name__ == "__main__":
print("线程安全与不安全操作对比演示")
# 先进行简单演示,快速展示基本概念
simple_demo()
# 运行增强演示,专门展示竞态条件
enhance_demo()
# 然后进行大规模对比演示
demo_safe_operation(num_threads=30, iterations_per_thread=200)
demo_unsafe_operation(num_threads=30, iterations_per_thread=200)
print("\n结论:在多线程环境中,必须使用锁或其他同步机制来保护共享资源,")
print(" 否则可能导致数据竞争,产生不正确的结果。即使在某些情况下")
print(" 看似没有问题,但在高并发或更复杂的场景下,线程安全问题会显现出来。")
print(" 良好的多线程编程习惯是始终使用适当的同步机制来保护共享数据。")
执行结果示例:
python
线程安全与不安全操作对比演示
=== 简单安全操作演示 ===
Data: 1
Data: 2
Data: 3
Data: 4
Data: 5
安全操作最终结果: 5
=== 简单不安全操作演示 ===
Unsafe data: 1
Unsafe data: 2
Unsafe data: 3
Unsafe data: 4
Unsafe data: 5
不安全操作最终结果: 5
=== 增强竞态条件演示 ===
这个演示特意设计来展示线程不安全操作中的竞态条件问题
启动50个线程,每个线程执行100次不安全递增操作...
增强演示结果:
期望结果: 5000
实际结果: 121
差值: 4879 (丢失了4879次更新)
这清楚地展示了竞态条件的问题!多个线程同时读取同一个值,
递增后又同时写回,导致某些递增操作被覆盖,最终结果小于期望值。
在实际应用中,这种问题可能导致数据不一致、财务计算错误等严重后果。
=== 演示安全操作 (使用锁) ===
初始化 shared_data = 0
最终 shared_data = 6000, 期望值 = 6000
结果 正确 (有锁保护)
执行时间: 0.0130秒
=== 演示不安全操作 (无锁保护) ===
初始化 shared_data = 0
结果 错误 (无锁保护)
丢失的更新次数: 5792
执行时间: 0.2201秒
结论:在多线程环境中,必须使用锁或其他同步机制来保护共享资源,
否则可能导致数据竞争,产生不正确的结果。即使在某些情况下
看似没有问题,但在高并发或更复杂的场景下,线程安全问题会显现出来。
良好的多线程编程习惯是始终使用适当的同步机制来保护共享数据。
示例3:使用超时
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
线程锁超时机制演示程序
这个程序展示了如何在线程编程中使用带超时参数的锁机制,
通过模拟资源竞争场景,演示线程在无法获取锁时如何优雅地处理超时情况。
"""
import threading
import time
# 创建锁对象,用于保护共享资源
resource_lock = threading.Lock()
# 标记资源是否正在被使用
resource_in_use = False
def access_resource(thread_id):
"""
尝试访问共享资源的线程函数
Args:
thread_id: 线程的唯一标识
此函数演示了:
1. 使用带超时的锁机制避免无限等待
2. 使用try-finally确保锁总是能被释放
3. 访问共享资源时的状态检查
"""
global resource_in_use
# 尝试在超时时间内获取锁
# timeout参数设置为2秒,表示最多等待2秒
if resource_lock.acquire(timeout=2):
try:
# 临界区:在锁的保护下检查和操作共享资源
if not resource_in_use:
# 标记资源为正在使用状态
resource_in_use = True
print(f"Thread {thread_id} acquired resource")
# 模拟资源使用,占用3秒(超过第二个线程的等待时间)
time.sleep(3)
# 使用完毕,恢复资源状态
resource_in_use = False
print(f"Thread {thread_id} released resource")
else:
# 理论上这里不会被执行,因为锁已经提供了互斥访问
# 但添加此检查作为额外的安全保障
print(f"Thread {thread_id}: Resource already in use")
finally:
# 确保无论如何都会释放锁,防止死锁
resource_lock.release()
else:
# 超时处理:当无法在规定时间内获取锁时执行
print(f"Thread {thread_id}: Failed to acquire lock within timeout")
# 程序入口
if __name__ == "__main__":
# 测试超时机制
# 创建两个线程尝试访问同一个资源
t1 = threading.Thread(target=access_resource, args=(1,))
t2 = threading.Thread(target=access_resource, args=(2,))
# 启动第一个线程
t1.start()
# 让第一个线程先开始运行,创造资源竞争条件
time.sleep(1)
# 启动第二个线程
t2.start()
# 等待两个线程都完成
t1.join()
t2.join()
print("\n程序执行完毕。此示例展示了线程锁超时机制如何避免无限等待。")
执行结果:
python
Thread 1 acquired resource
Thread 1 released resource
Thread 2: Failed to acquire lock within timeout
程序执行完毕。此示例展示了线程锁超时机制如何避免无限等待。
上下文管理器方式(推荐)
使用 with 语句是更安全和简洁的方式:
import threading
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
self.lock = threading.Lock()
def deposit(self, amount):
with self.lock: # 自动处理锁的获取和释放
self.balance += amount
print(f"Deposited {amount}, balance: {self.balance}")
def withdraw(self, amount):
with self.lock:
if self.balance >= amount:
self.balance -= amount
print(f"Withdrew {amount}, balance: {self.balance}")
return True
else:
print(f"Insufficient funds: {amount}, balance: {self.balance}")
return False
# 使用示例
account = BankAccount(100)
def transaction(account, action, amount):
if action == "deposit":
account.deposit(amount)
elif action == "withdraw":
account.withdraw(amount)
# 创建多个交易线程
threads = []
transactions = [
("deposit", 50),
("withdraw", 30),
("withdraw", 100),
("deposit", 200)
]
for action, amount in transactions:
t = threading.Thread(target=transaction, args=(account, action, amount))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final balance: {account.balance}")
常见问题和最佳实践
1. 死锁问题
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
避免嵌套锁导致的死锁示例程序
这个程序演示了:
1. 如何使用一致的锁获取顺序避免死锁
2. 使用threading.RLock作为另一种解决方案
3. 通过实际运行示例展示死锁问题和解决方案
"""
import threading
import time
import random
# 创建两个独立的锁
lock1 = threading.Lock()
lock2 = threading.Lock()
# 创建可重入锁(Reentrant Lock)
reentrant_lock1 = threading.RLock()
reentrant_lock2 = threading.RLock()
# 用于演示的数据
shared_counter = 0
shared_message = ""
def problematic_function(order):
"""
可能导致死锁的函数
Args:
order: 获取锁的顺序,'AB'表示先获取lock1再获取lock2,'BA'则相反
这个函数展示了:
- 当两个线程以不同顺序获取多个锁时,可能导致循环等待条件
- 这是死锁的经典场景:资源互斥、保持和等待、不可剥夺、循环等待
"""
try:
thread_id = threading.current_thread().name
if order == 'AB':
print(f"{thread_id} 尝试获取 lock1")
with lock1:
print(f"{thread_id} 获取到 lock1")
# 故意添加随机延迟,增加死锁的可能性
time.sleep(random.uniform(0.1, 0.5))
print(f"{thread_id} 尝试获取 lock2")
with lock2:
print(f"{thread_id} 获取到 lock2")
# 临界区操作
global shared_counter
shared_counter += 1
time.sleep(0.1) # 模拟工作
print(f"{thread_id} 完成临界区操作")
elif order == 'BA':
print(f"{thread_id} 尝试获取 lock2")
with lock2:
print(f"{thread_id} 获取到 lock2")
# 故意添加随机延迟,增加死锁的可能性
time.sleep(random.uniform(0.1, 0.5))
print(f"{thread_id} 尝试获取 lock1")
with lock1:
print(f"{thread_id} 获取到 lock1")
# 临界区操作
global shared_message
shared_message = "Completed"
time.sleep(0.1) # 模拟工作
print(f"{thread_id} 完成临界区操作")
except Exception as e:
print(f"{thread_id} 发生错误: {e}")
def safe_function_consistent_order():
"""
安全的函数 - 使用一致的锁获取顺序
这个函数通过:
- 始终以相同的顺序(先lock1再lock2)获取锁
- 无论哪个线程执行此函数,都遵循相同的顺序规则
- 这打破了死锁的'循环等待'条件
"""
thread_id = threading.current_thread().name
print(f"{thread_id} 按一致顺序尝试获取锁")
# 始终以相同顺序获取锁
with lock1:
print(f"{thread_id} 获取到 lock1")
time.sleep(random.uniform(0.1, 0.3)) # 模拟工作
with lock2:
print(f"{thread_id} 获取到 lock2")
# 临界区操作
global shared_counter
shared_counter += 1
print(f"{thread_id} 安全完成操作,counter = {shared_counter}")
def reentrant_lock_demo():
"""
使用可重入锁(RLock)的演示
RLock允许同一线程多次获取同一个锁,避免了在递归或
复杂调用结构中因重复获取同一把锁而导致的死锁问题
"""
thread_id = threading.current_thread().name
# 在RLock中,可以递归获取同一个锁
print(f"{thread_id} 第一次获取 RLock")
with reentrant_lock1:
print(f"{thread_id} 第一次获取成功")
# 同一线程可以再次获取同一个RLock
print(f"{thread_id} 第二次获取同一个 RLock")
with reentrant_lock1:
print(f"{thread_id} 第二次获取成功 (RLock允许递归获取)")
# 使用第二个RLock
with reentrant_lock2:
print(f"{thread_id} 获取到第二个 RLock")
# 临界区操作
global shared_message
shared_message = "RLock demo completed"
print(f"{thread_id} 使用RLock完成操作")
def demonstrate_deadlock():
"""
演示潜在的死锁情况
此函数启动两个线程,它们以不同顺序获取锁,很可能导致死锁
注意:由于线程调度的不确定性,有时可能不会立即发生死锁
"""
print("\n=== 演示潜在死锁情况 ===")
print("警告:这个演示可能会导致死锁,请耐心等待观察结果")
t1 = threading.Thread(target=problematic_function, args=('AB',), name="Thread-A")
t2 = threading.Thread(target=problematic_function, args=('BA',), name="Thread-B")
t1.start()
t2.start()
# 设置一个较短的超时等待,避免程序永久卡住
t1.join(timeout=5)
t2.join(timeout=5)
# 检查线程是否仍然活着(可能已经死锁)
if t1.is_alive() or t2.is_alive():
print("\n⚠️ 警告:线程可能已经进入死锁状态!")
print("这就是为什么我们需要遵循一致的锁获取顺序或使用RLock")
else:
print("\n这次没有发生死锁(可能是因为线程调度的顺序较好)")
print("但在高并发环境下,以不同顺序获取锁很容易导致死锁")
def demonstrate_safe_approach():
"""
演示安全的锁使用方法 - 一致的锁获取顺序
"""
print("\n=== 演示安全的锁使用方法 - 一致的获取顺序 ===")
global shared_counter
shared_counter = 0
threads = []
for i in range(3):
t = threading.Thread(
target=safe_function_consistent_order,
name=f"Safe-Thread-{i+1}"
)
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print(f"\n所有线程安全完成,最终 counter = {shared_counter}")
print("通过一致的锁获取顺序,我们可以避免死锁问题")
def demonstrate_rlock_approach():
"""
演示使用可重入锁(RLock)的方法
"""
print("\n=== 演示使用可重入锁(RLock) ===")
threads = []
for i in range(2):
t = threading.Thread(
target=reentrant_lock_demo,
name=f"RLock-Thread-{i+1}"
)
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print(f"\n所有使用RLock的线程完成,最终 message = '{shared_message}'")
print("RLock允许同一线程递归获取锁,适用于复杂的调用结构")
def print_deadlock_prevention_tips():
"""
打印避免死锁的最佳实践提示
"""
print("\n" + "="*60)
print("避免死锁的最佳实践:")
print("1. 始终以相同的顺序获取锁")
print("2. 保持锁的持有时间尽可能短")
print("3. 使用超时机制(lock.acquire(timeout=seconds))")
print("4. 对于递归或复杂调用结构,考虑使用RLock")
print("5. 限制同时持有的锁的数量")
print("6. 使用高级同步原语,如Queue、Event等")
print("7. 考虑使用第三方并发库,如concurrent.futures")
print("="*60)
# 程序入口
if __name__ == "__main__":
print("Python多线程死锁问题及解决方案演示\n")
# 按顺序运行各个演示
demonstrate_deadlock() # 演示死锁风险
demonstrate_safe_approach() # 演示安全的锁获取顺序
demonstrate_rlock_approach() # 演示RLock使用
print_deadlock_prevention_tips() # 打印最佳实践
print("\n演示完成!")
执行结果:
python
Python多线程死锁问题及解决方案演示
=== 演示潜在死锁情况 ===
警告:这个演示可能会导致死锁,请耐心等待观察结果
Thread-A 尝试获取 lock1
Thread-A 获取到 lock1
Thread-B 尝试获取 lock2
Thread-B 获取到 lock2
Thread-B 尝试获取 lock1
Thread-A 尝试获取 lock2
⚠️ 警告:线程可能已经进入死锁状态!
这就是为什么我们需要遵循一致的锁获取顺序或使用RLock
=== 演示安全的锁使用方法 - 一致的获取顺序 ===
Safe-Thread-1 按一致顺序尝试获取锁
Safe-Thread-2 按一致顺序尝试获取锁
Safe-Thread-3 按一致顺序尝试获取锁
2. 使用 RLock(可重入锁)
python
import threading
import time
class SafeCounter:
def __init__(self):
self.value = 0
self.rlock = threading.RLock() # 可重入锁
def increment(self):
with self.rlock:
print(f"线程 {threading.current_thread().name} 获取了外层锁")
self._inner_increment()
def _inner_increment(self):
with self.rlock: # 同一个线程可以多次获取 RLock
print(f"线程 {threading.current_thread().name} 获取了内层锁")
self.value += 1
time.sleep(0.1) # 模拟耗时操作
print(f"线程 {threading.current_thread().name} 完成了内层操作")
def test_reentrant_lock():
"""测试可重入锁的基本功能"""
counter = SafeCounter()
print("\n=== 测试1: 可重入锁允许同一线程多次获取 ===")
# 单线程测试 - 验证可重入性
def single_thread_task():
counter.increment()
thread1 = threading.Thread(target=single_thread_task, name="线程1")
thread1.start()
thread1.join()
print(f"计数器最终值: {counter.value}")
print("✅ 测试通过: 同一线程成功多次获取了可重入锁")
def test_multithreading_reentrant_lock():
"""测试多线程环境下的可重入锁"""
counter = SafeCounter()
print("\n=== 测试2: 多线程环境下的可重入锁 ===")
def thread_task():
for _ in range(3): # 每个线程调用3次
counter.increment()
threads = []
for i in range(2): # 创建2个线程
thread = threading.Thread(target=thread_task, name=f"线程{i+1}")
threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
print(f"计数器最终值: {counter.value}")
expected = 2 * 3 # 2个线程,每个执行3次
print(f"期望值: {expected}")
if counter.value == expected:
print("✅ 测试通过: 多线程环境下可重入锁正确保护了共享资源")
else:
print("❌ 测试失败: 多线程操作出现了不一致")
def compare_with_normal_lock():
"""对比普通锁和可重入锁的区别"""
print("\n=== 测试3: 对比普通锁和可重入锁 ===")
# 使用普通锁的类 - 会发生死锁
class DeadlockingCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock() # 普通锁
def increment(self):
with self.lock:
print("普通锁: 尝试获取外层锁...")
try:
self._inner_increment() # 这里会尝试再次获取同一个锁,导致死锁
except RuntimeError as e:
print(f"普通锁: 捕获到错误: {e}")
def _inner_increment(self):
with self.lock: # 普通锁: 同一线程无法再次获取
self.value += 1
# 测试普通锁死锁情况
print("\n测试普通锁(会导致死锁):")
deadlock_counter = DeadlockingCounter()
try:
deadlock_counter.increment()
print("注意: 普通锁测试没有产生预期的RuntimeError,这可能取决于Python版本")
except Exception as e:
print(f"捕获到异常: {type(e).__name__}: {e}")
# 再次展示RLock的正确行为
print("\n测试可重入锁(正常工作):")
rlock_counter = SafeCounter()
try:
rlock_counter.increment()
print("可重入锁工作正常")
except Exception as e:
print(f"错误: {type(e).__name__}: {e}")
def rlock_deadlock_prevention_demo():
"""演示RLock如何防止某些类型的死锁"""
print("\n=== 测试4: RLock防止死锁的场景演示 ===")
# 假设有两个资源需要按不同顺序获取
rlock1 = threading.RLock()
rlock2 = threading.RLock()
def task_a():
"""任务A: 先获取rlock1,再获取rlock2"""
try:
with rlock1:
print("任务A获取了锁1")
time.sleep(0.1) # 增加死锁可能性
with rlock2:
print("任务A获取了锁2")
# 这里可以嵌套调用其他函数,这些函数可能需要再次获取这些锁
nested_function()
except Exception as e:
print(f"任务A发生错误: {e}")
def nested_function():
"""嵌套函数可能需要再次获取已获取的锁"""
with rlock1: # 如果是普通锁,这里会导致死锁
print("嵌套函数再次获取了锁1")
with rlock2:
print("嵌套函数再次获取了锁2")
# 启动任务
thread_a = threading.Thread(target=task_a, name="任务A线程")
thread_a.start()
thread_a.join()
print("✅ 演示完成: RLock允许在复杂嵌套调用中避免死锁")
def show_rlock_vs_lock():
"""展示RLock和Lock的关键区别"""
print("\n=== 可重入锁(RLock) vs 普通锁(Lock) ===")
print("\n1. 主要区别:")
print(" - Lock: 同一线程不能重复获取同一个锁")
print(" - RLock: 同一线程可以多次获取同一个锁")
print("\n2. 适用场景:")
print(" - Lock: 简单的临界区保护,无嵌套调用")
print(" - RLock: 复杂调用链,可能存在递归或嵌套获取锁的情况")
print("\n3. 性能考虑:")
print(" - Lock比RLock性能略好")
print(" - RLock需要额外存储当前持有锁的线程ID和获取计数")
print("\n4. 最佳实践:")
print(" - 优先使用Lock,只在必要时使用RLock")
print(" - 避免过度使用可重入锁导致的设计复杂性")
print(" - 确保每次获取都有相应的释放操作")
if __name__ == "__main__":
print("=================================")
print(" Python 可重入锁 (RLock) 演示 ")
print("=================================")
print("这个程序演示了 threading.RLock 的特性和使用场景")
print("可重入锁允许同一个线程多次获取同一个锁,避免某些类型的死锁")
# 运行各种测试
test_reentrant_lock()
test_multithreading_reentrant_lock()
compare_with_normal_lock()
rlock_deadlock_prevention_demo()
show_rlock_vs_lock()
print("\n" + "="*50)
print("演示完成!")
print("="*50)
3. 锁的性能考虑
python
import threading
import time
import random
from concurrent.futures import ThreadPoolExecutor
# 粒度太细的锁会影响性能
class FineGrainedCounter:
def __init__(self):
self.counters = [0] * 10
self.locks = [threading.Lock() for _ in range(10)]
def increment(self, index):
with self.locks[index]:
self.counters[index] += 1
def get_total(self):
total = 0
for lock in self.locks:
with lock:
pass # 简单获取锁以确保一致性
for count in self.counters:
total += count
return total
# 粒度太粗的锁会成为瓶颈
class CoarseGrainedCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def increment_part1(self):
with self.lock:
# 不必要的长时间持有锁
time.sleep(0.1)
self.value += 1
def increment_part2(self):
with self.lock:
self.value += 1
def get_value(self):
with self.lock:
return self.value
# 优化的锁粒度实现
class OptimizedCounter:
def __init__(self):
self.value = 0
self.lock = threading.Lock()
def increment_optimized(self):
# 先执行不需要锁的操作
temp = 1 # 模拟一些预处理
# 最小化锁的持有时间
with self.lock:
self.value += temp
def get_value(self):
with self.lock:
return self.value
# 测试细粒度锁
def test_fine_grained(num_threads=100, iterations=100):
counter = FineGrainedCounter()
def worker():
for _ in range(iterations):
index = random.randint(0, 9)
counter.increment(index)
start_time = time.time()
with ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = [executor.submit(worker) for _ in range(num_threads)]
for future in futures:
future.result()
end_time = time.time()
total = counter.get_total()
expected = num_threads * iterations
print(f"细粒度锁测试:")
print(f" 执行时间: {end_time - start_time:.4f} 秒")
print(f" 实际计数: {total}, 预期计数: {expected}")
print(f" 结果{'正确' if total == expected else '错误'}")
return end_time - start_time
# 测试粗粒度锁(慢操作)
def test_coarse_grained_slow(num_threads=10, iterations=2):
counter = CoarseGrainedCounter()
def worker():
for _ in range(iterations):
counter.increment_part1()
start_time = time.time()
with ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = [executor.submit(worker) for _ in range(num_threads)]
for future in futures:
future.result()
end_time = time.time()
value = counter.get_value()
expected = num_threads * iterations
print(f"\n粗粒度锁测试(慢操作):")
print(f" 执行时间: {end_time - start_time:.4f} 秒")
print(f" 实际计数: {value}, 预期计数: {expected}")
print(f" 结果{'正确' if value == expected else '错误'}")
print(f" 注意: 长时间持有锁导致线程阻塞严重")
return end_time - start_time
# 测试粗粒度锁(快操作)
def test_coarse_grained_fast(num_threads=100, iterations=100):
counter = CoarseGrainedCounter()
def worker():
for _ in range(iterations):
counter.increment_part2()
start_time = time.time()
with ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = [executor.submit(worker) for _ in range(num_threads)]
for future in futures:
future.result()
end_time = time.time()
value = counter.get_value()
expected = num_threads * iterations
print(f"\n粗粒度锁测试(快操作):")
print(f" 执行时间: {end_time - start_time:.4f} 秒")
print(f" 实际计数: {value}, 预期计数: {expected}")
print(f" 结果{'正确' if value == expected else '错误'}")
return end_time - start_time
# 测试优化的锁粒度
def test_optimized(num_threads=100, iterations=100):
counter = OptimizedCounter()
def worker():
for _ in range(iterations):
counter.increment_optimized()
start_time = time.time()
with ThreadPoolExecutor(max_workers=num_threads) as executor:
futures = [executor.submit(worker) for _ in range(num_threads)]
for future in futures:
future.result()
end_time = time.time()
value = counter.get_value()
expected = num_threads * iterations
print(f"\n优化锁粒度测试:")
print(f" 执行时间: {end_time - start_time:.4f} 秒")
print(f" 实际计数: {value}, 预期计数: {expected}")
print(f" 结果{'正确' if value == expected else '错误'}")
return end_time - start_time
# 锁粒度影响分析
def analyze_lock_granularity():
print("="*60)
print(" 线程锁粒度对比分析演示 ")
print("="*60)
print("本程序演示了不同锁粒度对多线程性能的影响")
print("锁粒度过细: 过多的锁对象,增加开销")
print("锁粒度过粗: 线程争用激烈,性能瓶颈")
print("优化粒度: 最小化锁持有时间,合理划分临界区")
print()
# 运行各种测试
fine_time = test_fine_grained()
slow_time = test_coarse_grained_slow()
fast_time = test_coarse_grained_fast()
optimized_time = test_optimized()
print("\n" + "="*60)
print("性能对比总结:")
print(f"细粒度锁: {fine_time:.4f} 秒")
print(f"粗粒度锁(快): {fast_time:.4f} 秒")
print(f"优化锁粒度: {optimized_time:.4f} 秒")
print(f"粗粒度锁(慢): {slow_time:.4f} 秒")
print()
print("结论:")
print("1. 最小化锁的持有时间是提升性能的关键")
print("2. 细粒度锁适合数据结构的不同部分有较少关联时")
print("3. 粗粒度锁在锁竞争不激烈时可能更简单高效")
print("4. 最佳实践是在保证线程安全的前提下,尽可能减少锁的范围")
print("="*60)
if __name__ == "__main__":
analyze_lock_granularity()
执行结果:
python
============================================================
线程锁粒度对比分析演示
============================================================
本程序演示了不同锁粒度对多线程性能的影响
锁粒度过细: 过多的锁对象,增加开销
锁粒度过粗: 线程争用激烈,性能瓶颈
优化粒度: 最小化锁持有时间,合理划分临界区
细粒度锁测试:
执行时间: 0.0303 秒
实际计数: 10000, 预期计数: 10000
结果正确
粗粒度锁测试(慢操作):
执行时间: 2.0170 秒
实际计数: 20, 预期计数: 20
结果正确
注意: 长时间持有锁导致线程阻塞严重
粗粒度锁测试(快操作):
执行时间: 0.0138 秒
实际计数: 10000, 预期计数: 10000
结果正确
优化锁粒度测试:
执行时间: 0.0098 秒
实际计数: 10000, 预期计数: 10000
结果正确
============================================================
性能对比总结:
细粒度锁: 0.0303 秒
粗粒度锁(快): 0.0138 秒
优化锁粒度: 0.0098 秒
粗粒度锁(慢): 2.0170 秒
结论:
1. 最小化锁的持有时间是提升性能的关键
2. 细粒度锁适合数据结构的不同部分有较少关联时
3. 粗粒度锁在锁竞争不激烈时可能更简单高效
4. 最佳实践是在保证线程安全的前提下,尽可能减少锁的范围
============================================================
与其他同步原语的比较
| 同步原语 | 特点 | 适用场景 |
|---|---|---|
| Lock | 互斥锁,不可重入 | 基本的互斥访问 |
| RLock | 可重入锁 | 递归调用需要锁的场景 |
| Semaphore | 计数信号量 | 限制并发数量 |
| Event | 事件通知 | 线程间通信 |
| Condition | 条件变量 | 复杂的线程同步 |
总结
- Lock 是最基本的线程同步机制,确保同一时间只有一个线程访问共享资源
- 优先使用上下文管理器 (
with语句)来自动管理锁的生命周期 - 注意避免死锁,保持一致的锁获取顺序
- 合理设计锁的粒度,平衡安全性和性能
- 在 I/O 操作期间避免长时间持有锁,以免影响程序响应性
正确使用 threading.Lock 可以有效防止竞态条件,保证多线程程序的数据一致性。