1. 程式人生 > 實用技巧 >多執行緒併發--競爭問題

多執行緒併發--競爭問題

前言: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()

題目出處:https://leetcode-cn.com/problems/print-in-order/