1. 概述
為了保證數據的一致性,在多線程編程中我們會用到鎖,使得在某一時間點,只有一個線程進入臨界區代碼。雖然不同的語言可能會提供不同的鎖接口,但是底層調用的都是操作系統的提供的鎖,不同的高級語言只是在操作系統的鎖機制基礎上進行了些封裝而已,要真正理解鎖,還是得看操作系統是怎么實現鎖的。
2. 鎖的本質
所謂的鎖,本質上只是內存中的一個整形數,不同的數值表示不同的狀態,比如1表示空閑狀態和加鎖狀態。加鎖時,判斷鎖是否空閑,如果空閑,修改為加鎖狀態,返回成功,如果已經上鎖,返回失敗,解鎖時,就把鎖狀態修改為空閑狀態。
加鎖和解鎖看起來都很簡單,但是os是怎么保證鎖操作本身的原子性呢? 在多核環境中,兩個核上的代碼同時申請一個鎖,兩個核同時讀取鎖變量,同時判斷鎖是空閑的,再各自修改鎖變量為上鎖狀態,都返回成功,這樣兩個核同時獲取到了鎖, 這種情況可能嗎? 當然是不可能的,那么os是通過什么手段來保證鎖操作本身的原子性的呢?我們可以把上鎖的過程具體表示為:
- 讀內存表示鎖的變量
- 判斷鎖的狀態
- 如果已經加鎖,返回失敗
- 把鎖設置為上鎖狀態,
- 返回成功
上面的每一個步驟都對應一條匯編語句,可以認為這每一步操作都是原子的,什么情況會導致兩個線程同時獲取到鎖?
- 中斷: 當線程A執行完第一步后,發生了中斷,os調度線程B,線程B也來加鎖並且加鎖成功,此時又發生中斷,OS調度線程A執行,從第二步開始,也加鎖成功。
- 多核: 見上面例子。
那么怎么解決呢? 能不能讓硬件做一種加鎖的原子操作呢? 大名鼎鼎的“test and set”指令就是做這個事情的,該指令將讀取內存、判斷和設置值作為一個原子操作。單核環境下,鎖的操作肯定是原子性了,多核呢?貌似還是不行,因為多個核心他們的鎖操作是沒有干擾的,都能夠同時執行“test and set”,還是會出現兩個線程同時獲取到鎖的情況, 所以硬件提供了鎖內存總線的機制,在鎖內存總線的狀態下執行“test and set”操作就可以保證一個只有一個核執行成功,也就保證了不會存在多線程獲取到鎖的情況。
3. 硬件上怎么實現的
前面提到,cpu會通過對總線加鎖的手段來解決多核同時獲取鎖的情況,它到時是怎么實現的呢? 在cpu芯片上有一個HLOCK Pin,可以通過發送指令來操作,將#HLOCK Pin電位拉低,並持續到這條指令執行完畢,從而將總線鎖住,這樣同一總線上的其他CPU就不能通過總線來訪問內存了。最開始這些功能是用來測試cpu的,后來被操作系統實現而封裝成各種功能:關鍵代碼段,信號量等。
在加鎖的代碼編譯成匯編后,會有個lock指令前綴:
Causes the processor's LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal insures that the processor has exclusive use of any shared memory while the signal is asserted.
lock會使得緊跟在其后的指令變成atomic instruction,暫時的鎖一下總線,指令執行完,總線就解鎖了。
4. 小結
在硬件層面,cpu提供了原子操作、鎖內存總線等機制,OS根據這幾個cpu硬件機制就能夠實現鎖,在基於鎖,就能實現各種各樣的同步機制(信號量、消息等等),要理解os提供的各種同步手段,需要先理解os是怎么實現鎖的。
作者:tracy_668
鏈接:https://www.jianshu.com/p/61490effab35
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。