【趣話計算機底層技術】一個故事看懂各種鎖

我是一個線程,一個賣票程序的線程。

自從我們線程誕生以來,同一個進程地址空間裏允許有多個執行流一起執行,效率提升的同時,也引來了很多麻煩。

我們賣票線程的工作很簡單,比如票的總數是100,每賣一張就減1,直到變成0售完爲止。

以前單線程的時候沒啥問題,但多個線程一起執行的時候就發現,有些傢伙讀取到票數是100,減1後變成99,還沒等他把票數寫回去,另外有別的線程去讀也是100,也做了同樣的事,結果賣了兩張票,票數才減了1張,一天下來,多賣了很多票,氣的人類差點想砸了我們。

原子操作

我們把這問題反饋給了操作系統大哥,他給我們的解決方案是:讀取票數->票數減1->寫回票數這三個步驟不能被拆分,中途不能被打斷,他說這個叫原子操作

他給了我們一套原子操作的手冊,裏面不止有減法,還有加法、位運算,只要調用手冊裏的原子操作函數,就能保證邏輯的正確性。

我很好奇操作系統大哥是如何實現這個過程的原子化的,他告訴我,如果CPU只有一個核很好辦,執行原子操作的時候,他不切換線程就可以。而如果是有多個核,就需要CPU來幫忙了。

你還別說,我們用這個原子操作來賣票後,再也沒有發生超額賣票的問題。

自旋鎖

有一天,我們賣票程序進行了升級,不再是直接讀取票數->票數減1->寫回票數這麼簡單,還需要安排座位,現在變成了:

 我們一翻手冊,沒有哪個原子操作函數能滿足我們的功能,畢竟安排座位這個操作是咱們賣票程序自己的事兒,一點也不通用,操作系統大哥肯定不會專門爲我們開發一個原子操作函數。

我們只好再一次求助操作系統大哥,他一看就說:“你們這個問題,用自旋鎖就可以解決”

鎖?我們還是第一次聽說這玩意,不知道是什麼意思。

操作系統告訴我們,讓我們回去創建一個鎖,這鎖裏面有一個狀態標記來表示當前有沒有被佔用,所有線程在執行賣票操作之前,都得先去獲取這個鎖,如果鎖被佔用了,線程就會阻塞在獲取函數那裏,獲取函數內部會不斷循環去檢查,直到別的線程釋放後才返回。

 因爲獲取鎖的時候線程會一直循環檢查狀態,所以這鎖也叫自旋鎖。

現在,我們的工作流程變成了:

我們又可以愉快地賣票了!

互斥鎖

我們的業務發展很快,後來,我們用上了數據庫,把票的數量寫到了數據庫裏面,於是我們的工作流程變成了:

 本以爲只是把票數從本地內存搬到了數據庫,應該沒什麼不一樣,結果發現我們運行經常出錯,還莫名其妙地被殺掉進程。

我們向操作系統大哥大倒苦水,沒想到他卻說:“你們還好意思訴苦,你們獲取自旋鎖後搞那麼耗時的操作,讓別的線程一直自旋等待,把CPU都跑得飛起,風扇都轉個不停···”

我們都羞愧地低下了頭,原來,把票數從本地內存搬到了數據庫,差別這麼大。

操作系統又接着說道:“自旋鎖因爲會使得線程一直阻塞自旋,沒有讓出CPU,所以只適合處理比較快速的場合,像讀取數據庫這種很耗時的操作,不能用它,會白白浪費CPU時間!”

我們又詢問:“有沒有別的不浪費CPU的辦法呢?”

操作系統大哥又給我們介紹了一個叫互斥鎖的東西,聽說獲取這個鎖的時候,線程不會去自旋檢查,而是把自己放到這把鎖的等待隊列中,然後就交出CPU執行權限,進入睡眠,看起來就跟阻塞一樣,等到後面別的線程釋放鎖之後,再去喚醒它的等待隊列裏的線程繼續運行。

回去以後我們就用上了互斥鎖,現在我們的流程變成了這樣:

 我們又又能愉快地賣票了!

條件變量

有一天,我們的賣票程序又進行了升級,21-100號票價格比較便宜,交給其他線程來賣,1-20號票價格比較貴,交給我來賣。

現在,我們不同的線程賣的票不一樣了。

別的線程的流程是這樣:

 而我的流程是這樣:

 使用互斥鎖倒也沒什麼問題,可就是我經常拿到鎖以後發現票號還大於20,不該我處理,只好默默的釋放鎖,白白把我喚醒,卻什麼也沒幹!

空手而回的次數多了以後,我又去請教操作系統大哥,能不能讓我指定一個條件,等條件滿足了再喚醒我運行,別讓我白跑。

沒想到還真有辦法!操作系統告訴了我一個叫條件變量的東西,等待條件變量的線程平時阻塞着,別的線程發現條件滿足之後,就將條件變量激活,那個時候等待的線程纔會被喚醒。

回去之後,我跟我的小夥伴兒們商量了一下,我們創建了一個條件變量,等到它們發現票號小於等於20的時候,就把條件變量激活,我就會被喚醒,再也不用白跑了!

信號量

互斥鎖和條件變量真是好東西,幫了我們大忙,不僅幫我們解決了賣票的問題,我們還在其他很多地方使用它,我們遇到的絕大多數同步和互斥問題都可以用它們來解決。

直到有一天,我們遇到了一個新的問題。

我們的票越賣越好,從100到1000,票的數量越來越多,來找我們買票的客戶也越來越多。

因爲每次售票都要訪問數據庫,連接它的線程有些多,那傢伙有些喫不消了。

希望我們控制一下訪問數據庫的線程數量。

我們很自然的想到了互斥鎖,只有拿到鎖的線程才能去訪問數據庫。

可這互斥鎖名叫互斥,只能允許一個線程拿到鎖,總不能只允許一個線程訪問數據庫吧,那可不行。所以我們希望這個名額能放寬,允許多個線程同時獲得鎖。

我們再一次找到了操作系統大哥,大哥拿出了他的絕招——信號量

他告訴我們,這信號量就像一個升級版的互斥鎖,它裏面有一個計數器,可以用來指定最多允許多少個線程同時獲得它。

這正是我們想要的鎖!

很快我們用上了信號量,我們又又又能愉快地賣票了!

Tips:在信號量一節中,實際上數據庫能承受的併發量遠不止這點,這裏爲了故事情節需要,弱化了數據庫的併發承受能力。

往期推薦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章