Python Lock 详解

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 条件变量 复杂的线程同步

总结

  1. Lock 是最基本的线程同步机制,确保同一时间只有一个线程访问共享资源
  2. 优先使用上下文管理器with 语句)来自动管理锁的生命周期
  3. 注意避免死锁,保持一致的锁获取顺序
  4. 合理设计锁的粒度,平衡安全性和性能
  5. 在 I/O 操作期间避免长时间持有锁,以免影响程序响应性

正确使用 threading.Lock 可以有效防止竞态条件,保证多线程程序的数据一致性。

相关推荐
Hard but lovely2 小时前
linux: pthread库---posix线程创建使用接口&&状态
linux·开发语言·c++
先做个垃圾出来………2 小时前
创建Flask app应用对象
后端·python·flask
yyy(十一月限定版)2 小时前
C语言——堆
c语言·开发语言·算法
澜莲花2 小时前
python图色之opencv基础
开发语言·图像处理·python·opencv
喜欢吃燃面2 小时前
算法竞赛中的数据结构:图
开发语言·数据结构·c++·学习·算法
黎雁·泠崖2 小时前
C 语言动态内存管理入门:malloc/calloc/realloc/free 核心函数详解
c语言·开发语言
哈市雪花2 小时前
记录一次cmake无法正确使用vcpkg的问题
开发语言·c++
Yue丶越2 小时前
【C语言】文件操作
服务器·c语言·开发语言
工业互联网专业2 小时前
国内python职位数据分析_flask+spider
python·数据分析·flask·毕业设计·源码·课程设计·spider