防止死鎖的加鎖機制
在多線程程序中,死鎖問題很大一部分是由於線程同時獲取多個鎖造成的。舉個例子:一個線程獲取了第一個鎖,然後在獲取第二個鎖的 時候發生阻塞,那麼這個線程就可能阻塞其他線程的執行,從而導致整個程序假死。 解決死鎖問題的一種方案是爲程序中的每一個鎖分配一個唯一的id,然後只允許按照升序規則來使用多個鎖,這個規則使用上下文管理器 是非常容易實現的,示例如下:
import threading
from contextlib import contextmanager
# Thread-local state to stored information on locks already acquired
_local = threading.local()
@contextmanager
def acquire(*locks):
# Sort locks by object identifier
locks = sorted(locks, key=lambda x: id(x))
# Make sure lock order of previously acquired locks is not violated
acquired = getattr(_local,'acquired',[])
if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
raise RuntimeError('Lock Order Violation')
# Acquire all of the locks
acquired.extend(locks)
_local.acquired = acquired
try:
for lock in locks:
lock.acquire()
yield
finally:
# Release locks in reverse order of acquisition
for lock in reversed(locks):
lock.release()
del acquired[-len(locks):]
import threading
from contextlib import contextmanager
# Thread-local state to stored information on locks already acquired
_local = threading.local()
@contextmanager
def acquire(*locks):
# Sort locks by object identifier
locks = sorted(locks, key=lambda x: id(x))
# Make sure lock order of previously acquired locks is not violated
acquired = getattr(_local,'acquired',[])
if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
raise RuntimeError('Lock Order Violation')
# Acquire all of the locks
acquired.extend(locks)
_local.acquired = acquired
try:
for lock in locks:
lock.acquire()
yield
finally:
# Release locks in reverse order of acquisition
for lock in reversed(locks):
lock.release()
del acquired[-len(locks):]
import threading
import time
x_lock = threading.Lock()
y_lock = threading.Lock()
def thread_1():
while True:
with acquire(x_lock, y_lock):
print('Thread-1')
time.sleep(0.1)
def thread_2():
while True:
with acquire(y_lock, x_lock):
print('Thread-2')
time.sleep(0.1)
t1 = threading.Thread(target=thread_1)
t1.daemon = True
t1.start()
t2 = threading.Thread(target=thread_2)
t2.daemon = True
t2.start()
如果你執行這段代碼,你會發現它即使在不同的函數中以不同的順序獲取鎖也沒有發生死鎖。 其關鍵在於,在第一段代碼中,我們對這些鎖進行了排序。通過排序,使得不管用戶以什麼樣的順序來請求鎖,這些鎖都會按照固定的順序被獲取
哲學家就餐問題
在進程獲取鎖的時候會嚴格按照對象id升序排列獲取,經過數學證明,這樣保證程序不會進入 死鎖狀態
五位哲學家圍坐在一張桌子前,每個人 面前有一碗飯和一隻筷子。在這裏每個哲學家可以看做是一個獨立的線程,而每隻筷子可以看做是一個鎖。每個哲學家可以處在靜坐、 思考、喫飯三種狀態中的一個。需要注意的是,每個哲學家喫飯是需要兩隻筷子的,這樣問題就來了:如果每個哲學家都拿起自己左邊的筷子, 那麼他們五個都只能拿着一隻筷子坐在那兒,直到餓死。此時他們就進入了死鎖狀態。 下面是一個簡單的使用死鎖避免機制解決“哲學家就餐問題”的實現:
import threading
# The philosopher thread
def philosopher(left, right):
while True:
with acquire(left,right):
print(threading.currentThread().name, 'eating')
# The chopsticks (represented by locks)
NSTICKS = 5
chopsticks = [threading.Lock() for n in range(NSTICKS)]
# Create all of the philosophers
for n in range(NSTICKS):
t = threading.Thread(target=philosopher,
args=(chopsticks[n],chopsticks[(n+1) % NSTICKS]))
t.start()