今天主要通過多讀單寫的例子來說下讀寫鎖的原理
概念
多讀單寫,簡單說,就是對資源的訪問分為兩種狀態,一種是讀操作,另一種是寫操作。由應用程式提示鎖應該做哪種操作。當為讀模式時,所有的寫動作被懸掛,而讀請求被允許通過,而寫動作時,所有操作被懸掛。並且,讀寫切換時,有足夠的狀態等待,直到真正安全時,才會切換動作。
如下圖所示:
業務場景舉例
比如現在有 a、b、c、d、e、f、g 6個執行緒,其中a、b、c、g 4個執行緒之行讀請求,e、f 2個執行緒之行寫請求,如何保證讀寫安全?
分析:1、讀寫請求是可以在多個執行緒進行的
2、寫請求時,所有的請求都會被停止即懸掛
解決:使用讀寫鎖
**:demo裡面的**就是業務場景的表達,即有多個執行緒同時執行讀寫請求的業務場景
- (void下面的 read 和 write 方法裡,就是讀寫鎖的使用)demo );
dispatch_async(self.queue, ^);
dispatch_barrier_async(self.queue, ^);
dispatch_async(self.queue, ^);}}
- (void讀寫鎖的原理在 aqs 中,通過 int 型別的全域性變數 state 來表示同步狀態,即用 state 來表示鎖。)read
- (void
)write
reentrantreadwritelock 也是通過 aqs 來實現鎖的,但是 reentrantreadwritelock有兩把鎖:讀鎖和寫鎖,它們保護的都是同乙個資源,那麼如何用乙個共享變數來區分鎖是寫鎖還是讀鎖呢?答案就是按位拆分。
由於 state 是 int 型別的變數,在記憶體中占用4個位元組,也就是32位。將其拆分為兩部分:高16位和低16位,其中高16位用來表示讀鎖狀態,低16位用來表示寫鎖狀態。
如下圖所示:
寫鎖加鎖的原理
獲取寫鎖的流程
c == 0表示鎖還沒有被任何執行緒占用
w 寫鎖的數量
如果 c==0,標記鎖成功後,表述獲取寫鎖成功
如果 c!=0 && w==0,表示讀鎖在占用鎖,所以獲取鎖失敗
如果 c!=0 && w!=0,表示寫鎖在占用鎖,此時就需要判斷訪問該鎖的執行緒是否和占用該鎖的執行緒為同一執行緒,如果不為同一執行緒就返回失敗;如果為同一執行緒,則判斷重入的數量,數量為超過就返回成功,否則丟擲異常
protected final boolean tryacquire(int讀鎖加鎖的原理獲取讀鎖的流程c == 0 表示鎖還沒有被任何執行緒占用acquires) /**
* 1. writershouldblock()方法的作用是判斷當前執行緒是否應該阻塞,對於公平的寫鎖和非公平寫鎖的具體實現不一樣。
* 對於非公平寫鎖而言,直接返回false,因為非公平鎖獲取鎖之前不需要去判斷是否排隊
* 對於公平鎖寫鎖而言,它會判斷同步佇列中是否有人在排隊,有人排隊,就返回true,表示當前執行緒需要阻塞。無人排隊就返回false。
* * 2. 當writershouldblock()返回true時,表示當前執行緒還不能直接獲取鎖,因此tryacquire()方法直接返回false。
* 當writershouldblock()返回false時,表示當前執行緒可以嘗試去獲取鎖,因此會執行if判斷中後面的邏輯,即通過cas方法嘗試去修改同步變數的值,
* 如果修改同步變數成功,則表示當前執行緒獲取到了鎖,最終tryacquire()方法會返回true。如果修改失敗,那麼tryacquire()會返回false,表示獲取鎖失敗。 *
*/if (writershouldblock() ||
!compareandsetstate(c, c +acquires))
return
false
;setexclusiveownerthread(current);
return
true
;}
r 讀鎖的數量
w = exclusivecount(c) 寫鎖的數量
如果c!=0 && w!=0,表示寫鎖在占用鎖,改執行緒就未獲取到讀鎖所以立即執行fulltryacquireshared(current);
如果c!=0 && r!=0,表示鎖被寫執行緒占用
protected final int tryacquireshared(int如果 r==0, firstreader = currentunused)
else
if (firstreader ==current)
else
//返回1表示獲取讀鎖成功
return1;
}//當if中的三個判斷均不滿足時,就會執行到這兒,呼叫fulltryacquireshared()方法嘗試獲取鎖
return
fulltryacquireshared(current);
}
如果 r!=0 && firstreader為當前執行緒,firstreaderholdcount++
如果第乙個獲取到讀鎖的執行緒不是當前執行緒就記錄當前執行緒的獲取鎖的數量,並讓請求執行緒獲得鎖
讀鎖獲取鎖失敗後會迴圈的去執行下面這個方法,直到滿足相應的條件才會 return 退出,否則一直迴圈
final int問題當有100個執行緒來併發的進行讀寫請求,其中有99個執行緒是進行讀請求,只有乙個執行緒是進行寫請求(假設寫請求的編號為20)fulltryacquireshared(thread current) else
if(readershouldblock()) else
}if (rh.count == 0
)
return -1
; }
}if (sharedcount(c) ==max_count)
throw
new error("
maximum lock count exceeded");
//嘗試設定同步變數的值,只要設定成功了,就表示當前執行緒獲取到了鎖,然後就設定鎖的獲取次數等相關資訊
if (compareandsetstate(c, c +shared_unit))
else
if (firstreader ==current)
else
return1;}}
}
先有1-19執行緒進行了讀請求
然後第20執行緒進行了寫請求
又來21-100執行緒80個執行緒進行讀請求
結果是第20執行緒等到所有讀執行緒執行完了才能執行寫請求
從而導致寫鎖飢餓問題
總結
多讀單寫在實際開發過程中是非常常見的,不同的開發語言有不同的解決方式,但是大體的實現思路是差不多的。我們會使用讀寫鎖,但是其讀寫鎖的原理也需要明白和理解。
python程式設計學習資源乾貨、
python+selenium框架web的ui自動化、
python+unittest框架api自動化、
資源和** 免費送啦~
新增關注,讓我們一起共同成長!
多執行緒之讀寫鎖
之前沒真正使用讀寫鎖,看到別人對讀寫鎖的解釋總感覺一頭霧水。今天親自敲 實驗之後,才明了,原來如此。網上沒有一篇文章是能描述出自己理解的樣子,所以將自己的思路記下來。先提出疑問,邊自答邊找思路,有了思路,再回頭去執行一下 就清晰明了了。如果你急著想要一句話概括讀寫鎖,那我會告訴你 讀鎖是加在讀方法裡...
多執行緒程式設計之讀寫鎖
讀寫問題是乙個經典的同步問題,主要針對保護很少更新的資料結構這種同步情況。假設有乙個用於儲存dns條目快取的表,它用來將網域名稱解析為相應的ip位址。通常,乙個給定的dns條目將在很長一段時間裡保持不變 在許多情況下,dns條目會保持數年不變。雖然隨著使用者訪問不同的 新的條目可能會被不時地新增到表...
多執行緒程式設計之讀寫鎖
多執行緒程式設計之讀寫鎖 pthread是 posix threads 的簡稱,是posix的執行緒標準。pthread讀寫鎖把對共享資源的訪問者分為讀者和寫者,讀者只對共享資源進行讀訪問,寫者只對共享資源進行寫操作。在互斥機制,讀者和寫者都需要獨立獨佔互斥量以獨佔共享資源,在讀寫鎖機制下,允許同時...