MST
星途 面试题库

面试题:Python多线程数据库操作的死锁问题及解决

在Python多线程并发执行数据库操作时,可能会出现死锁现象。假设你使用`sqlite3`数据库和`threading`模块进行多线程编程,描述死锁可能出现的场景,并给出至少两种避免死锁的解决方案。
27.3万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

死锁可能出现的场景

假设有两个线程 Thread1Thread2,它们都需要访问并修改两个数据库表 TableATableB

  1. 场景一

    • Thread1 首先获取对 TableA 的锁(在SQLite中,这可能是隐式的,例如开始一个事务并对 TableA 进行写操作),然后试图获取对 TableB 的锁。
    • 与此同时,Thread2 首先获取对 TableB 的锁,然后试图获取对 TableA 的锁。由于两个线程都持有对方需要的资源且不释放,就会导致死锁。
  2. 场景二

    • 存在多个线程(如 Thread1Thread2Thread3 等),并且线程之间存在复杂的资源依赖关系。例如 Thread1 持有 TableA 的锁并请求 TableB 的锁,Thread2 持有 TableB 的锁并请求 TableC 的锁,Thread3 持有 TableC 的锁并请求 TableA 的锁,形成一个循环依赖,从而导致死锁。

避免死锁的解决方案

  1. 按顺序获取锁
    • 在Python中,确保所有线程以相同的顺序获取数据库资源。例如,如果所有线程都先获取 TableA 的锁,然后再获取 TableB 的锁,就不会出现上述第一种死锁场景。示例代码如下:
import sqlite3
import threading

# 创建数据库连接
conn = sqlite3.connect('test.db')
lock_table_a = threading.Lock()
lock_table_b = threading.Lock()


def thread_function():
    with lock_table_a:
        # 对TableA进行数据库操作
        cursor = conn.cursor()
        cursor.execute('INSERT INTO TableA (column1) VALUES (?)', ('data',))
        conn.commit()
        with lock_table_b:
            # 对TableB进行数据库操作
            cursor.execute('INSERT INTO TableB (column1) VALUES (?)', ('data',))
            conn.commit()


# 创建并启动线程
thread = threading.Thread(target = thread_function)
thread.start()
thread.join()
conn.close()
  1. 使用超时机制
    • 在获取锁(或数据库资源)时设置超时时间。如果在超时时间内无法获取到锁,线程可以释放已获取的锁并进行其他操作,避免一直等待导致死锁。例如:
import sqlite3
import threading
import time


# 创建数据库连接
conn = sqlite3.connect('test.db')
lock_table_a = threading.Lock()
lock_table_b = threading.Lock()


def thread_function():
    if lock_table_a.acquire(timeout = 5):
        try:
            # 对TableA进行数据库操作
            cursor = conn.cursor()
            cursor.execute('INSERT INTO TableA (column1) VALUES (?)', ('data',))
            conn.commit()
            if lock_table_b.acquire(timeout = 5):
                try:
                    # 对TableB进行数据库操作
                    cursor.execute('INSERT INTO TableB (column1) VALUES (?)', ('data',))
                    conn.commit()
                finally:
                    lock_table_b.release()
            else:
                print('Failed to acquire lock_table_b, releasing lock_table_a')
                lock_table_a.release()
        finally:
            lock_table_a.release()


# 创建并启动线程
thread = threading.Thread(target = thread_function)
thread.start()
thread.join()
conn.close()
  1. 减少锁的持有时间
    • 尽量缩短持有数据库锁(或资源)的时间。例如,在进行数据库操作时,将操作尽量合并,减少多次获取和释放锁的操作。示例如下:
import sqlite3
import threading


# 创建数据库连接
conn = sqlite3.connect('test.db')
lock = threading.Lock()


def thread_function():
    with lock:
        cursor = conn.cursor()
        cursor.execute('INSERT INTO TableA (column1) VALUES (?)', ('data',))
        cursor.execute('INSERT INTO TableB (column1) VALUES (?)', ('data',))
        conn.commit()


# 创建并启动线程
thread = threading.Thread(target = thread_function)
thread.start()
thread.join()
conn.close()

通过这种方式,减少了不同线程之间资源竞争的时间窗口,降低死锁发生的概率。