MST

星途 面试题库

面试题:Python多线程资源同步中的死锁问题及解决策略

在一个Python多线程程序中,存在多个线程竞争多个资源,有可能会产生死锁。请分析死锁产生的四个必要条件,并结合Python多线程实际代码(如使用`threading.Lock`等同步工具)举例说明死锁是如何发生的。然后给出至少两种有效的避免死锁的策略,并对每种策略进行代码实现。
12.0万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

死锁产生的四个必要条件

  1. 互斥条件:资源在某一时刻只能被一个线程占用。例如,一个文件不能同时被两个线程写入。
  2. 占有并等待条件:一个线程持有至少一个资源,并正在等待获取其他线程持有的资源。比如线程A持有资源R1,同时等待资源R2,而R2被线程B持有。
  3. 不可剥夺条件:资源只能由持有它的线程主动释放,不能被其他线程强行剥夺。即线程A持有的资源,其他线程不能直接抢走。
  4. 循环等待条件:存在一组线程(T1, T2, ..., Tn),T1等待T2持有的资源,T2等待T3持有的资源,以此类推,Tn等待T1持有的资源,形成一个循环等待的环。

死锁示例代码

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()
    print('Thread 1 released lock2')
    lock1.release()
    print('Thread 1 released lock1')


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


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

t1.start()
t2.start()

t1.join()
t2.join()

在上述代码中,thread1先获取lock1,然后尝试获取lock2,而thread2先获取lock2,然后尝试获取lock1。如果thread1获取了lock1thread2获取了lock2,就会发生死锁,因为它们相互等待对方释放资源。

避免死锁的策略及代码实现

  1. 破坏占有并等待条件:一次性获取所有需要的资源,而不是逐步获取。
import threading

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


def thread1():
    with threading.Lock():
        lock1.acquire()
        lock2.acquire()
        print('Thread 1 acquired both locks')
        lock2.release()
        lock1.release()


def thread2():
    with threading.Lock():
        lock1.acquire()
        lock2.acquire()
        print('Thread 2 acquired both locks')
        lock2.release()
        lock1.release()


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

t1.start()
t2.start()

t1.join()
t2.join()

在这个代码中,使用一个全局锁来确保在获取lock1lock2时不会发生占有并等待的情况。

  1. 破坏循环等待条件:给资源分配一个序号,线程按照序号递增的顺序获取资源。
import threading

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


def thread1():
    locks = [lock1, lock2]
    locks.sort(key=lambda x: id(x))
    for lock in locks:
        lock.acquire()
    print('Thread 1 acquired locks in order')
    for lock in reversed(locks):
        lock.release()


def thread2():
    locks = [lock2, lock1]
    locks.sort(key=lambda x: id(x))
    for lock in locks:
        lock.acquire()
    print('Thread 2 acquired locks in order')
    for lock in reversed(locks):
        lock.release()


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

t1.start()
t2.start()

t1.join()
t2.join()

这里通过对锁按照id排序,确保所有线程都按照相同顺序获取锁,从而避免循环等待。