Python中的线程安全与同步原语(Lock、RLock、Semaphore、Condition、Event)
字数 1536 2025-11-16 23:55:00

Python中的线程安全与同步原语(Lock、RLock、Semaphore、Condition、Event)

1. 问题背景

在Python多线程编程中,多个线程可能同时访问共享资源(如变量、文件、数据库连接等),导致数据不一致或逻辑错误。例如:

import threading

counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1

threads = []
for _ in range(10):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(counter)  # 结果可能小于1000000

问题分析
counter += 1 并非原子操作,实际执行时会被拆解为多个步骤(读取值、计算新值、写入结果),线程切换可能导致数据覆盖。


2. 解决方案:同步原语

Python的threading模块提供了多种同步工具,确保线程间协调访问共享资源。

2.1 Lock(互斥锁)

作用:保证同一时刻只有一个线程能执行关键代码段。
使用步骤

  1. 创建锁:lock = threading.Lock()
  2. 获取锁:lock.acquire()
  3. 释放锁:lock.release()
  4. 推荐使用上下文管理器(自动释放锁):
def increment():
    global counter
    for _ in range(100000):
        with lock:  # 自动获取和释放锁
            counter += 1

lock = threading.Lock()  # 修复代码:锁需在函数外定义

注意:锁必须被所有线程共享,且需避免重复释放锁(会导致RuntimeError)。


2.2 RLock(可重入锁)

问题场景:如果一个线程在持有锁的情况下再次尝试获取同一把锁,普通Lock会阻塞自身(死锁)。
RLock特性:允许同一线程多次获取锁,需释放相同次数才能彻底解锁。

rlock = threading.RLock()

def nested_lock():
    with rlock:  # 第一次获取锁
        with rlock:  # 同一线程可再次获取
            print("双重锁定安全")

threading.Thread(target=nested_lock).start()

2.3 Semaphore(信号量)

作用:控制同时访问资源的线程数量(如连接池限流)。
示例:限制最多3个线程同时运行:

semaphore = threading.Semaphore(3)

def task():
    with semaphore:
        print(f"{threading.current_thread().name} 执行任务")
        # 模拟耗时操作
        time.sleep(1)

for i in range(10):
    threading.Thread(target=task).start()

2.4 Condition(条件变量)

作用:让线程等待特定条件满足后再执行,常用于生产者-消费者模型。
核心方法

  • wait():释放锁并等待通知
  • notify(n):唤醒至少n个等待线程
  • notify_all():唤醒所有等待线程
queue = []
condition = threading.Condition()

def producer():
    with condition:
        queue.append("数据")
        condition.notify()  # 通知一个消费者

def consumer():
    with condition:
        while not queue:
            condition.wait()  # 队列空时等待
        item = queue.pop()
        print(f"消费: {item}")

2.5 Event(事件)

作用:线程间简单通信机制,一个线程发出信号,其他线程等待信号。
核心方法

  • set():设置事件为True
  • clear():重置事件为False
  • wait():阻塞直到事件为True
event = threading.Event()

def waiter():
    print("等待事件触发...")
    event.wait()  # 阻塞直到set()被调用
    print("事件已触发!")

def setter():
    time.sleep(2)
    event.set()  # 唤醒所有等待线程

threading.Thread(target=waiter).start()
threading.Thread(target=setter).start()

3. 对比与选型指南

同步原语 适用场景 特点
Lock 互斥访问简单共享资源 轻量级,不支持重入
RLock 嵌套锁需求(如递归函数) 同一线程可多次获取
Semaphore 限制并发线程数 计数器控制
Condition 复杂条件等待(如生产者-消费者) 需与锁结合使用,支持等待/通知机制
Event 简单信号通知(如启动/停止信号) 布尔标志,一次性通信

4. 实战注意事项

  1. 避免死锁:按固定顺序获取锁,或使用超时机制(lock.acquire(timeout=5))。
  2. 性能影响:同步原语会引入开销,尽量缩小锁的覆盖范围(如减少锁内代码)。
  3. GIL限制:在CPU密集型任务中,多线程可能无法充分利用多核,建议改用多进程(multiprocessing模块)。

通过合理选择同步工具,可确保多线程程序既安全又高效。

Python中的线程安全与同步原语(Lock、RLock、Semaphore、Condition、Event) 1. 问题背景 在Python多线程编程中,多个线程可能同时访问共享资源(如变量、文件、数据库连接等),导致数据不一致或逻辑错误。例如: 问题分析 : counter += 1 并非原子操作,实际执行时会被拆解为多个步骤(读取值、计算新值、写入结果),线程切换可能导致数据覆盖。 2. 解决方案:同步原语 Python的 threading 模块提供了多种同步工具,确保线程间协调访问共享资源。 2.1 Lock(互斥锁) 作用 :保证同一时刻只有一个线程能执行关键代码段。 使用步骤 : 创建锁: lock = threading.Lock() 获取锁: lock.acquire() 释放锁: lock.release() 推荐使用上下文管理器(自动释放锁): 注意 :锁必须被所有线程共享,且需避免重复释放锁(会导致 RuntimeError )。 2.2 RLock(可重入锁) 问题场景 :如果一个线程在持有锁的情况下再次尝试获取同一把锁,普通Lock会阻塞自身(死锁)。 RLock特性 :允许同一线程多次获取锁,需释放相同次数才能彻底解锁。 2.3 Semaphore(信号量) 作用 :控制同时访问资源的线程数量(如连接池限流)。 示例 :限制最多3个线程同时运行: 2.4 Condition(条件变量) 作用 :让线程等待特定条件满足后再执行,常用于生产者-消费者模型。 核心方法 : wait() :释放锁并等待通知 notify(n) :唤醒至少n个等待线程 notify_all() :唤醒所有等待线程 2.5 Event(事件) 作用 :线程间简单通信机制,一个线程发出信号,其他线程等待信号。 核心方法 : set() :设置事件为True clear() :重置事件为False wait() :阻塞直到事件为True 3. 对比与选型指南 | 同步原语 | 适用场景 | 特点 | |------------|------------------------------------|------------------------------------| | Lock | 互斥访问简单共享资源 | 轻量级,不支持重入 | | RLock | 嵌套锁需求(如递归函数) | 同一线程可多次获取 | | Semaphore | 限制并发线程数 | 计数器控制 | | Condition | 复杂条件等待(如生产者-消费者) | 需与锁结合使用,支持等待/通知机制 | | Event | 简单信号通知(如启动/停止信号) | 布尔标志,一次性通信 | 4. 实战注意事项 避免死锁 :按固定顺序获取锁,或使用超时机制( lock.acquire(timeout=5) )。 性能影响 :同步原语会引入开销,尽量缩小锁的覆盖范围(如减少锁内代码)。 GIL限制 :在CPU密集型任务中,多线程可能无法充分利用多核,建议改用多进程( multiprocessing 模块)。 通过合理选择同步工具,可确保多线程程序既安全又高效。