您好,登錄后才能下訂單哦!
今天小編給大家分享一下Python模擬死鎖實例代碼分析的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
常見的例子是在銀行賬戶上:假如要在兩個銀行賬戶之間執行交易,你必須確保兩個賬戶都被鎖定,不受其他交易的影響,以達到正確的資金轉移量。在這里,這個類比并不完全成立--哲學家對應的是鎖定賬戶的交易(分叉)--但同樣的技術困難也會出現。
其他的例子包括電商秒殺系統,多個用戶搶一個商品,不允許一個數據庫被多個客戶同時修改。
死鎖也是由一個并發程序需要同時具備的條件來定義的,這樣才會發生死鎖。這些條件是由計算機科學家Edward G. Coffman, Jr .首先提出的,因此被稱為 Coffman 條件。這些條件如下:
至少有一個資源必須處于不可共享的狀態。這意味著該資源被一個單獨的進程(或線程)持有,不能被其他人訪問; 在任何時間內,該資源只能被單個的進程(或線程)訪問和持有。這個條件也被稱為相互排斥。
有一個進程(或線程)同時訪問一個資源并等待其他進程(或線程)持有的另一個資源。換句話說,這個進程(或線程)需要訪問兩個資源來執行其指令,其中一個它已經持有,另一個它正在等待其他進程(或線程)。這種情況被稱為保持和等待。
只有在有特定指令讓進程(或線程)釋放資源的情況下,才能由持有這些資源的進程(或線程)來釋放。這就是說,除非進程(或線程)自愿主動地釋放資源,否則該資源仍處于不可共享的狀態。這就是無搶占條件。
最后一個條件叫做循環等待。顧名思義,這個條件規定了一組進程(或線程)的存在,因此這組進程中的第一個進程(或線程)正在等待第二個進程(或線程)釋放資源,而第二個進程(或線程)又需要等待第三個進程(或線程);最后,這組進程中的最后一個進程(或線程)正在等待第一個進程。
造成線程死鎖的常見例子包括:
一個在自己身上等待的線程(例如,試圖兩次獲得同一個互斥鎖)
互相等待的線程(例如,A 等待 B,B 等待 A)
未能釋放資源的線程(例如,互斥鎖、信號量、屏障、條件、事件等)
線程以不同的順序獲取互斥鎖(例如,未能執行鎖排序)
導致死鎖的一個常見原因是線程在自己身上等待。
我們并不打算讓這種死鎖發生,例如,我們不會故意寫代碼,導致線程自己等待。相反,由于一系列的函數調用和變量的傳遞,這種情況會意外地發生。
一個線程可能會因為很多原因而在自己身上等待,比如:
等待獲得它已經獲得的互斥鎖
等待自己被通知一個條件
等待一個事件被自己設置
等待一個信號被自己釋放
開發一個 task()
函數,直接嘗試兩次獲取同一個 mutex 鎖。也就是說,該任務將獲取鎖,然后再次嘗試獲取鎖。
# task to be executed in a new thread def task(lock): print('Thread acquiring lock...') with lock: print('Thread acquiring lock again...') with lock: # will never get here pass
這將導致死鎖,因為線程已經持有該鎖,并將永遠等待自己釋放該鎖,以便它能再次獲得該鎖, task()
試圖兩次獲取同一個鎖并觸發死鎖。
在主線程中,可以創建鎖:
# create the mutex lock lock = Lock()
然后我們將創建并配置一個新的線程,在一個新的線程中執行我們的 task()
函數,然后啟動這個線程并等待它終止,而它永遠不會終止。
# create and configure the new thread thread = Thread(target=task, args=(lock,)) # start the new thread thread.start() # wait for threads to exit... thread.join()
完整代碼如下:
from threading import Thread from threading import Lock # task to be executed in a new thread def task(lock): print('Thread acquiring lock...') with lock: print('Thread acquiring lock again...') with lock: # will never get here pass # create the mutex lock lock = Lock() # create and configure the new thread thread = Thread(target=task, args=(lock,)) # start the new thread thread.start() # wait for threads to exit... thread.join()
運行結果如下:
首先創建鎖,然后新的線程被混淆并啟動,主線程阻塞,直到新線程終止,但它從未這樣做。
新線程運行并首先獲得了鎖。然后它試圖再次獲得相同的互斥鎖并阻塞。
它將永遠阻塞,等待鎖被釋放。該鎖不能被釋放,因為該線程已經持有該鎖。因此,該線程已經陷入死鎖。
該程序必須被強制終止,例如,通過 Control-C 殺死終端。
一個常見的例子就是兩個或多個線程互相等待。例如:線程 A 等待線程 B,線程 B 等待線程 A。
如果有三個線程,可能會出現線程循環等待,例如:
線程 A:等待線程 B
線程 B:等待線程 C
線程 C:等待線程 A
如果你設置了線程來等待其他線程的結果,這種死鎖是很常見的,比如在一個流水線或工作流中,子任務的一些依賴關系是不符合順序的。
from threading import current_thread from threading import Thread # task to be executed in a new thread def task(other): # message print(f'[{current_thread().name}] waiting on [{other.name}]...\n') other.join() # get the current thread main_thread = current_thread() # create the second thread new_thread = Thread(target=task, args=(main_thread,)) # start the new thread new_thread.start() # run the first thread task(new_thread)
首先得到主線程的實例 main_thread
,然后創建一個新的線程 new_thread
,并調用傳遞給主線程的 task()
函數。新線程返回一條信息并等待主線程停止,主線程用新線程的實例調用 task()
函數,并等待新線程的終止。每個線程都在等待另一個線程終止,然后自己才能終止,這導致了一個死鎖。
運行結果:
[Thread-1] waiting on [MainThread]...
[MainThread] waiting on [Thread-1]...
導致死鎖的一個常見原因是,兩個線程同時以不同的順序獲得鎖。例如,我們可能有一個受鎖保護的關鍵部分,在這個關鍵部分中,我們可能有代碼或函數調用受第二個鎖保護。
可能會遇到這樣的情況:一個線程獲得了鎖 1 ,然后試圖獲得鎖 2,然后有第二個線程調用獲得鎖 2 的功能,然后試圖獲得鎖 1。如果這種情況同時發生,線程 1 持有鎖 1,線程 2 持有鎖 2,那么就會有一個死鎖。
線程1: 持有鎖 1, 等待鎖 2
線程2 : 持有鎖 2, 等待鎖 1
from time import sleep from threading import Thread from threading import Lock # task to be executed in a new thread def task(number, lock1, lock2): # acquire the first lock print(f'Thread {number} acquiring lock 1...') with lock1: # wait a moment sleep(1) # acquire the next lock print(f'Thread {number} acquiring lock 2...') with lock2: # never gets here.. pass # create the mutex locks lock1 = Lock() lock2 = Lock() # create and configure the new threads thread1 = Thread(target=task, args=(1, lock1, lock2)) thread2 = Thread(target=task, args=(2, lock2, lock1)) # start the new threads thread1.start() thread2.start() # wait for threads to exit... thread1.join() thread2.join()
運行這個例子首先創建了兩個鎖。然后兩個線程都被創建,主線程等待線程的終止。
第一個線程接收 lock1 和 lock2 作為參數。它獲得了鎖 1 并 sleep。
第二個線程接收 lock2 和 lock1 作為參數。它獲得了鎖 2 并 sleep。
第一個線程醒來并試圖獲取鎖 2,但它必須等待,因為它已經被第二個線程獲取。第二個線程醒來并試圖獲取鎖 1,但它必須等待,因為它已經被第一個線程獲取。
結果是一個死鎖:
Thread 1 acquiring lock 1...
Thread 2 acquiring lock 1...
Thread 1 acquiring lock 2...
Thread 2 acquiring lock 2...
解決辦法是確保鎖在整個程序中總是以相同的順序獲得。這就是所謂的鎖排序。
導致死鎖的另一個常見原因是線程未能釋放一個資源。這通常是由線程在關鍵部分引發錯誤或異常造成的,這種方式會阻止線程釋放資源,包括:
未能釋放一個鎖
未能釋放一個信號器
未能到達一個 barrier
未能在一個條件上通知線程
未能設置一個事件
# example of a deadlock caused by a thread failing to release a lock from time import sleep from threading import Thread from threading import Lock # task to be executed in a new thread def task(lock): # acquire the lock print('Thread acquiring lock...') lock.acquire() # fail raise Exception('Something bad happened') # release the lock (never gets here) print('Thread releasing lock...') lock.release() # create the mutex lock lock = Lock() # create and configure the new thread thread = Thread(target=task, args=(lock,)) # start the new thread thread.start() # wait a while sleep(1) # acquire the lock print('Main acquiring lock...') lock.acquire() # do something... # release lock (never gets here) lock.release()
運行該例子時,首先創建鎖,然后創建并啟動新的線程。然后主線程阻塞。新線程運行。它首先獲得了鎖,然后引發了一個異常而失敗。該線程解開了鎖,但卻沒有解開鎖的代碼。新的線程終止了。最后,主線程被喚醒,然后試圖獲取鎖。由于鎖沒有被釋放,主線程永遠阻塞,導致了死鎖。
Thread acquiring lock...
Exception in thread Thread-1:
Traceback (most recent call last):
...
Exception: Something bad happened
Main acquiring lock...
以上就是“Python模擬死鎖實例代碼分析”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。