面试题答案
一键面试死锁可能出现的场景
假设有两个线程 Thread1
和 Thread2
,它们都需要访问并修改两个数据库表 TableA
和 TableB
。
-
场景一:
Thread1
首先获取对TableA
的锁(在SQLite中,这可能是隐式的,例如开始一个事务并对TableA
进行写操作),然后试图获取对TableB
的锁。- 与此同时,
Thread2
首先获取对TableB
的锁,然后试图获取对TableA
的锁。由于两个线程都持有对方需要的资源且不释放,就会导致死锁。
-
场景二:
- 存在多个线程(如
Thread1
、Thread2
、Thread3
等),并且线程之间存在复杂的资源依赖关系。例如Thread1
持有TableA
的锁并请求TableB
的锁,Thread2
持有TableB
的锁并请求TableC
的锁,Thread3
持有TableC
的锁并请求TableA
的锁,形成一个循环依赖,从而导致死锁。
- 存在多个线程(如
避免死锁的解决方案
- 按顺序获取锁:
- 在Python中,确保所有线程以相同的顺序获取数据库资源。例如,如果所有线程都先获取
TableA
的锁,然后再获取TableB
的锁,就不会出现上述第一种死锁场景。示例代码如下:
- 在Python中,确保所有线程以相同的顺序获取数据库资源。例如,如果所有线程都先获取
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()
- 使用超时机制:
- 在获取锁(或数据库资源)时设置超时时间。如果在超时时间内无法获取到锁,线程可以释放已获取的锁并进行其他操作,避免一直等待导致死锁。例如:
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()
- 减少锁的持有时间:
- 尽量缩短持有数据库锁(或资源)的时间。例如,在进行数据库操作时,将操作尽量合并,减少多次获取和释放锁的操作。示例如下:
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()
通过这种方式,减少了不同线程之间资源竞争的时间窗口,降低死锁发生的概率。