MST

星途 面试题库

面试题:Python多线程下的资源竞争与死锁深度分析及解决方案

详细描述在Python多线程编程中可能出现资源竞争和死锁的场景。假设你正在开发一个复杂的多线程应用,如何通过代码设计和使用同步原语(如锁、信号量、条件变量等)来预防死锁,同时高效地处理资源竞争问题?请给出具体的设计方案和代码示例,并分析其优缺点。
30.7万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

资源竞争场景

在Python多线程编程中,当多个线程同时访问和修改共享资源(如全局变量、文件、数据库连接等)时,就可能出现资源竞争。例如:

import threading

counter = 0

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

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

for t in threads:
    t.join()

print(counter)

上述代码中,两个线程同时对counter进行累加操作,由于CPU调度的不确定性,可能导致最终结果与预期不符,这就是资源竞争。

死锁场景

死锁通常发生在多个线程相互等待对方释放资源的情况下。例如:

import threading

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1():
    lock1.acquire()
    print("Thread 1 acquired lock1")
    lock2.acquire()
    print("Thread 1 acquired lock2")
    lock2.release()
    lock1.release()

def thread2():
    lock2.acquire()
    print("Thread 2 acquired lock2")
    lock1.acquire()
    print("Thread 2 acquired lock1")
    lock1.release()
    lock2.release()

t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)

t1.start()
t2.start()

t1.join()
t2.join()

在上述代码中,thread1先获取lock1thread2先获取lock2,然后它们都试图获取对方已持有的锁,从而导致死锁。

预防死锁和处理资源竞争的设计方案

  1. 使用锁(Lock):通过对共享资源的访问加锁,同一时间只有一个线程可以访问共享资源。
import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(1000000):
        lock.acquire()
        counter = counter + 1
        lock.release()

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

for t in threads:
    t.join()

print(counter)
  1. 使用信号量(Semaphore):信号量可以控制同时访问共享资源的线程数量。
import threading

semaphore = threading.Semaphore(2)  # 允许两个线程同时访问

def access_shared_resource():
    semaphore.acquire()
    try:
        # 访问共享资源的代码
        print(threading.current_thread().name, "accessed shared resource")
    finally:
        semaphore.release()

threads = []
for i in range(5):
    t = threading.Thread(target=access_shared_resource)
    threads.append(t)
    t.start()

for t in threads:
    t.join()
  1. 使用条件变量(Condition):条件变量用于线程间的复杂同步,当某个条件满足时,通知其他线程。
import threading

condition = threading.Condition()
resource = None

def producer():
    global resource
    with condition:
        resource = "Some data"
        condition.notify()  # 通知消费者数据已准备好

def consumer():
    with condition:
        condition.wait()  # 等待生产者通知
        print("Consumer got:", resource)

t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)

t2.start()
t1.start()

t1.join()
t2.join()

优缺点分析

  1. 锁(Lock)
    • 优点:简单直接,能有效防止资源竞争。
    • 缺点:如果使用不当,容易导致死锁;并且会降低程序并发度,因为同一时间只有一个线程能访问共享资源。
  2. 信号量(Semaphore)
    • 优点:可以控制同时访问资源的线程数量,适用于对资源访问数量有限制的场景,能在一定程度上提高并发度。
    • 缺点:同样存在死锁风险,如果信号量的获取和释放顺序不当。
  3. 条件变量(Condition)
    • 优点:适用于线程间需要复杂同步的场景,能实现更灵活的线程间通信和同步。
    • 缺点:使用相对复杂,需要仔细设计条件判断和通知逻辑,否则容易出现逻辑错误。