多線程中,對共享資源進行通路,為了防止并發引起的相關問題,通常都是引入鎖的機制來處理并發問題。
擷取到資源的線程A對這個資源加鎖,其他線程比如B要通路這個資源首先要獲得鎖,而此時A持有這個資源的鎖,隻有等待線程A邏輯執行完,釋放鎖,這個時候B才能擷取到資源的鎖進而擷取到該資源。
這個過程中,A一直持有着資源的鎖,那麼沒有擷取到鎖的其他線程比如B怎麼辦?通常就會有兩種方式:
1. 一種是沒有獲得鎖的程序就直接進入阻塞(BLOCKING),這種就是互斥鎖
2. 另外一種就是沒有獲得鎖的程序,不進入阻塞,而是一直循環着,看是否能夠等到A釋放了資源的鎖。
上述的兩種方式,學術上,就有幾種不同的定義方式,大學的時候 學習的是C++, 《C++ 11》中就有這樣的描述:
自旋鎖(spin lock)是一種非阻塞鎖,也就是說,如果某線程需要擷取鎖,但該鎖已經被其他線程占用時,該線程不會被挂起,而是在不斷的消耗CPU的時間,不停的試圖擷取鎖。
互斥量(mutex)是阻塞鎖,當某線程無法擷取鎖時,該線程會被直接挂起,該線程不再消耗CPU時間,當其他線程釋放鎖後,作業系統會激活那個被挂起的線程,讓其投入運作。
而《linux核心設計與實作》經常提到兩種态,一種是核心态,一種是使用者态,對于自旋鎖來說,自旋鎖使線程處于使用者态,而互斥鎖需要重新配置設定,進入到核心态。這裡大家對核心态和使用者态有個初步的認知就行了,使用者态比較輕,核心态比較重。使用者态和核心态這個也是linux中必備的知識基礎,借鑒這個,可以進行很多程式設計語言API上的優化,就比如說javaio的部分,操作io的時候,先是要從使用者态,進入核心态,再用核心态去操作輸入輸出裝置的抽象,這裡減少使用者态到核心态的轉換就是新io的一部分優化,後面再聊。
wiki中的定義如下:
自旋鎖是計算機科學用于多線程同步的一種鎖,線程反複檢查鎖變量是否可用。由于線程在這一過程中保持執行,是以是一種忙等待。
自旋鎖避免了程序上下文的排程開銷,是以對于線程隻會阻塞很短時間的場合是有效的。是以作業系統的實作在很多地方往往用自旋鎖。Windows作業系統提供的輕型讀寫鎖(SRW Lock)内部就用了自旋鎖。顯然,單核CPU不适于使用自旋鎖,這裡的單核CPU指的是單核單線程的CPU,因為,在同一時間隻有一個線程是處在運作狀态,假設運作線程A發現無法擷取鎖,隻能等待解鎖,但因為A自身不挂起,是以那個持有鎖的線程B沒有辦法進入運作狀态,隻能等到作業系統分給A的時間片用完,才能有機會被排程。這種情況下使用自旋鎖的代價很高。(紅字部分是我給wiki編輯的詞條,單核CPU不适合自旋鎖,這個也隻是針對單核單線程的情況,現在的技術基本單核都是支援多線程的)
為什麼要使用自旋鎖
互斥鎖有一個缺點,他的執行流程是這樣的 托管代碼 - 使用者态代碼 - 核心态代碼、上下文切換開銷與損耗,假如擷取到資源鎖的線程A立馬處理完邏輯釋放掉資源鎖,如果是采取互斥的方式,那麼線程B從沒有擷取鎖到擷取鎖這個過程中,就要使用者态和核心态排程、上下文切換的開銷和損耗。是以就有了自旋鎖的模式,讓線程B就在使用者态循環等着,減少消耗。
自旋鎖比較适用于鎖使用者保持鎖時間比較短的情況,這種情況下自旋鎖的效率要遠高于互斥鎖。