多執行緒併發--競爭問題
前言:GIL
GIL的全稱是Global Interpreter Lock(全域性直譯器鎖),來源是python設計之初的考慮,為了資料安全所做的決定。某個執行緒想要執行,必須先拿到GIL,我們可以把GIL看作是“通行證”,並且在一個python程序中,GIL只有一個。拿不到通行證的執行緒,就不允許進入CPU執行。GIL只在cpython中才有,因為cpython呼叫的是c語言的原生執行緒,所以他不能直接操作cpu,只能利用GIL保證同一時間只能有一個執行緒拿到資料。而在pypy和jpython中是沒有GIL的。
在python中多執行緒的併發只能每一刻執行一個執行緒,那麼就會產生一個競爭資源問題,執行緒是共享資料的。
在這用力扣的題目解釋用同步鎖機制解決競爭條件
假設有一個方法withdraw(amount)
,如果請求量小於當前餘額,則從當前餘額中減去請求量,然後返回餘額。方法定義如下:
1 balance = 500 2 def withdraw(amount): 3 if (amount < balance): 4 balance -= amount 5 return balance
正常結果餘額不能為負。但此時我用兩個執行緒不同引數執行該方法時,如:執行緒1 withdraw(400)和執行緒2 withdraw(200)的執行流程如下圖:
對於這種問題,我們可以利用鎖來解決,將一個執行緒的關鍵執行部分加上鎖,執行完在釋放。如下圖
在3、4步的時候,執行緒2執行到來關鍵部分所以加上來鎖,因此執行緒1此時處於休眠狀態,5、6步的時候同理。
例題:--同步鎖
三個不同的執行緒將會共用一個Foo例項。
執行緒 A 將會呼叫 one() 方法
執行緒 B 將會呼叫two() 方法
執行緒 C 將會呼叫 three() 方法
請設計修改程式,以確保 two() 方法在 one() 方法之後被執行,three() 方法在 two() 方法之後被執行。
思路:利用依賴關係,建立一個變數firstJobDone協調one()和two()之間的執行順序,在建立一個變數secondJobDone協調two()和three()之間的執行關係
- 首先初始化共享變數 firstJobDone 和 secondJobDone,初始值表示所有方法未執行。
- 方法 first() 沒有依賴關係,可以直接執行。在方法最後更新變數 firstJobDone 表示該方法執行完成。
- 方法 second() 中,檢查 firstJobDone 的狀態。如果未更新則進入等待狀態,否則執行方法 second()。在方法末尾,更新變數 secondJobDone 表示方法 second() 執行完成。
- 方法 third() 中,檢查 secondJobDone 的狀態。與方法 second() 類似,執行 third() 之前,需要先等待 secondJobDone 的狀態。
from threading import Lock class Foo: def __init__(self): self.firstjobDone = Lock() self.secondjobDone = Lock() self.firstjobDone.acquire() # 請求加鎖 self.secondjobDone.acquire() def first(self, printFirst: 'Callable[[], None]') -> None: # printFirst() outputs "first". Do not change or remove this line. printFirst() self.firstjobDone.release() # 釋放鎖 def second(self, printSecond: 'Callable[[], None]') -> None: # printSecond() outputs "second". Do not change or remove this line. with self.firstjobDone: # 上下文管理器,可獲取和釋放鎖 printSecond() self.secondjobDone.release() def third(self, printThird: 'Callable[[], None]') -> None: # printThird() outputs "third". Do not change or remove this line. with self.secondjobDone: printThird()