Go源碼解讀——互斥鎖與讀寫鎖

互斥鎖Mutex

type Mutex struct {// 表示互斥鎖狀態state int32// 表示信號量,協程阻塞等待該信號量,解鎖的協程釋放信號量從而喚醒等待信號量的協程sema  uint32
}

  • Locked: 表示該Mutex是否已被鎖定,0:沒有鎖定 1:已被鎖定

  • Woken: 表示是否有協程已被喚醒,0:沒有協程喚醒 1:已有協程喚醒,正在加鎖過程中

  • Starving:表示該Mutex是否處于饑餓狀態,0:沒有饑餓 1:饑餓狀態,說明有協程阻塞了超過1ms

  • Waiter: 表示阻塞等待鎖的協程個數,協程解鎖時根據此值來判斷是否需要釋放信號量

簡單加鎖

判斷Locked標志位是否為0,如果是0則把Locked置1,代表加鎖成功

加鎖被阻塞

加鎖前,如果Locked為1,則將Waiter+1,表示此協程阻塞等待,直到Locked為0后被喚醒

簡單解鎖

如果沒有其他協程阻塞等待加鎖,Waiter為0,則直接把Locked置0,

解鎖并喚醒協程

如果Waiter大于0,有其他協程阻塞等待加鎖,則當前協程解鎖:將Locked置0;釋放一個信號量,喚醒一個阻塞協程

自旋過程

自旋相當于CPU空轉,sleep一小段時間。自旋過程當前協程會持續探測Locked是否改為0,如果改為0則可以直接運行,不必進入阻塞狀態。可以充分利用CPU,避免協程切換

問題:如果加鎖的協程特別多,每次都通過自旋獲得鎖,那么之前被阻塞的進程將很難獲得鎖,從而進入饑餓狀態。1.8版本以來增加了一個狀態,即Mutex的Starving狀態。這個狀態下不會自旋,一旦有協程釋放鎖,那么一定會喚醒一個協程并成功加鎖

模式
normal模式

默認模式,該模式下,協程如果加鎖不成功不會立即轉入阻塞排隊,而是判斷是否滿足自旋的條件,如果滿足則會啟動自旋過程,嘗試搶鎖

starvation模式

處于饑餓模式下,不會啟動自旋過程,也即一旦有協程釋放了鎖,那么一定會喚醒協程,被喚醒的協程將會成功獲取鎖,同時也會把等待計數減1

讀寫鎖RWMutex

讀讀不互斥,讀寫互斥,寫寫互斥

  • P操作 信號量值 -1,如果小于0就阻塞等待;V操作 信號量值 +1,喚醒阻塞的線程

  • 讀者和寫者加鎖時,進行P操作;讀者和寫者釋放鎖時,進行V操作

  • 寫者加鎖和讀者釋放鎖,操作writerSem;讀者加鎖和寫者釋放鎖,操作readerSem

type RWMutex struct {// 控制寫鎖,獲得寫鎖首先要獲取該鎖,實現了寫寫互斥w           Mutex  // 寫阻塞等待的信號量,最后一個讀者釋放鎖時會釋放此信號量,通知寫者進行寫操作writerSem   uint32 // 讀阻塞等待的信號量,持有寫鎖的協程釋放鎖后會釋放此信號量,通知讀者進行讀操作readerSem   uint32 // 實現了讀寫互斥,讀讀不互斥// 只要有讀操作到來,則此字段+1(讀讀不互斥)// 1.記錄當前持有讀鎖(正在讀或者等待寫完再讀)的協程數量  2.<0代表有寫者在等(讀寫互斥)readerCount int32  // 記錄寫阻塞時需要等待多少個讀者釋放讀鎖(實現寫操作不會被餓死)readerWait  int32  
}
源碼
寫者加鎖Lock()
const rwmutexMaxReaders = 1 << 30func (rw *RWMutex) Lock() {// 先獲取互斥鎖rw.w.Lock()// 先將readerCount加一個很大的負數使其<0(表示當前有寫者正在等或執行),然后用r記錄原來的readerCountr := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders// r!=0說明當前還有讀者正在讀,隨后讓readerWait加上r(readerCount),表示要等待的讀者完成的個數if r != 0 && rw.readerWait.Add(r) != 0 {// runtime_SemacquireRWMutex()阻塞當前goroutine,直到某個條件滿足釋放信號量的操作// 當前寫者需要阻塞(writerSem信號量為0則阻塞),直到所有讀者釋放鎖(信號量大于0則執行)runtime_SemacquireRWMutex(&rw.writerSem, false, 0)}
}
  • 先獲取寫鎖

  • 將readerCount加一個很大的負數使其<0,表示當前有寫者正在等待或執行

  • 將readerWait加上原本的readerCount,表示當前寫操作之前要等待的讀操作數量

  • 如果readerCount和readerWait都不為0,則等最后一個讀操作執行完畢釋放writerSem信號量,再執行寫操作

讀者加鎖RLock()
func (rw *RWMutex) RLock() {// 讀者數量+1(先加上讀鎖),然后判斷如果小于0表示有寫者持有寫鎖,則不能讀者立即加鎖if rw.readerCount.Add(1) < 0 {// 讀者等待寫者信號量釋放runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)}
}
  • 先readerCount+1,將讀操作入隊排隊

  • 等寫操作執行完(釋放readerSem信號量),再執行讀操作

寫者釋放鎖UnLock()
 func (rw *RWMutex) Unlock() {// 在Lock加鎖的時候對readerCount減去了rwmutexMaxReaders,這時加回來還原恢復r := rw.readerCount.Add(rwmutexMaxReaders)// 加rwmutexMaxReaders之前,readerCount減去了rwmutexMaxReaders// 如果這個readerCount>=0,說明沒有寫者if r >= rwmutexMaxReaders {race.Enable()fatal("sync: Unlock of unlocked RWMutex")}// 當前寫鎖期間累積了多少個阻塞的讀者(readerCount),就釋放幾次readerSemfor i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem, false, 0)}// 釋放寫鎖rw.w.Unlock()
}
  • 恢復readerCount為原本的值

  • 有多少個readerCount(寫操作期間,后來的讀操作被阻塞等待,讀操作數量),就釋放幾次readerSem信號量,將所有等待的讀操作全部喚醒

  • 釋放寫鎖

讀者釋放鎖RUnLock()
func (rw *RWMutex) RUnlock() {// 先將readerCount減1,釋放讀鎖。如果readerCount<0,表示有寫者在等,則進入rUnlockSlow()if r := rw.readerCount.Add(-1); r < 0 {rw.rUnlockSlow(r)}
}func (rw *RWMutex) rUnlockSlow(r int32) {// 邊界問題處理// r+1 ==0 表示沒有讀者加鎖,卻調用了釋放讀鎖// r+1 == -rwmutexMaxReaders表示在沒有讀者加鎖,有寫者持有互斥鎖的情況下,卻釋放了讀鎖if r+1 == 0 || r+1 == -rwmutexMaxReaders {race.Enable()fatal("sync: RUnlock of unlocked RWMutex")}// readerWait-1,將寫者需要等待的讀者-1// 如果readerWait-1后為0,表示這是最后一個讀者,要發送信號量通知寫者if rw.readerWait.Add(-1) == 0 {runtime_Semrelease(&rw.writerSem, false, 1)}
}
  • 先釋放讀鎖(readerCount-1)

  • 如果有寫者在等(readerCount<0),并且當此讀者為寫操作需要等待的最后一個讀者時(readerWait-1==0),釋放writerSem信號量通知寫者進行寫操作

寫操作如何阻止讀操作(正在寫操作,則不能讀)

每次讀鎖定將readerCount值+1,每次解除讀鎖定將該值-1

寫鎖定進行時,會先將readerCount減去固定的大數,從而readerCount變成負值,此時再有讀鎖定到來時檢測到readerCount為負值,便知道有寫操作在進行,只好阻塞等待。而真實的讀操作個數并不會丟失,只需要將readerCount加上固定大數即可獲得

所以,寫操作將readerCount變成負值來阻止讀操作的

讀操作如何阻止寫操作(正在讀操作,則不能寫)

讀鎖定會先將readerCount加1,此時寫操作到來時發現讀者數量readerCount不為0,會阻塞等待所有讀操作結束

所以,讀操作通過readerCount來將來阻止寫操作的

為什么寫鎖定不會被餓死(寫優先)

寫操作要等待讀操作結束后才可以獲得鎖,寫操作等待期間可能還有新的讀操作持續到來,如果寫操作等待所有讀操作結束,很可能被餓死

  1. readerWait標記寫操作到來時,前面正在執行的讀操作的個數,等這些讀操作完畢后readerWait減為0,通知寫操作執行(由于只要有讀操作到來,都會將readerCount+1,所以無法知道讀操作的先后順序,這里用readerWait臨時記錄寫操作到來時前面的讀操作個數,實現了給寫操作“排隊”的效果,使寫操作不會被后續讀操作“插隊”)

寫操作到來時,會把readerCount值拷貝到readerWait中,用于標記排在寫操作前面的讀者個數

寫操作前面的讀操作結束后,除了會遞減readerCount,還會遞減RWMutex.readerWait,當readerWait值變為0時(寫操作前面的讀操作都已結束),喚醒寫操作

? ? ? 2. 同時寫操作一旦到來,就會將readerCount改為負值,表示有寫操作在等待,使得寫操作不會被后續到來的讀操作搶占

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92096.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92096.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92096.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux(centos)安全狗

sdui進入操作頁面 [rootlocalhost safedog_an_linux64_2.8.32947]# sdui維護 查看、啟動或停止服務。 [rootiZbp1f0xuq9rc41s6gdvfyZ /]# systemctl status safedog [rootiZbp1f0xuq9rc41s6gdvfyZ /]# systemctl start safedog [rootiZbp1f0xuq9rc41s6gdvfyZ /]# systemct…

ES9 / ES2018 正則表達式增強

? 一、命名捕獲組&#xff08;Named Capture Groups&#xff09;給捕獲結果起名字&#xff0c;更易讀、更易維護。&#x1f539; 傳統寫法&#xff08;位置識別&#xff09;&#xff1a;const result /(\d{4})-(\d{2})-(\d{2})/.exec("2025-07-31"); console.log(…

深入Java開發:Token的全方位解析與實戰指南(下)

深入Java開發&#xff1a;Token的全方位解析與實戰指南&#xff08;下&#xff09; 上一篇 深入Java開發&#xff1a;Token的全方位解析與實戰指南&#xff08;上&#xff09; 五、Token 的生命周期與管理 5.1 Token 的生命周期狀態 Token 的生命周期涵蓋了從創建到最終失效…

第二十四天(數據結構:棧和隊列)隊列實踐請看下一篇

棧和隊列棧 &#xff1a; 是限定在表尾進行插入和刪除操作的線性表實現是一回事&#xff0c;但是必須要滿足棧的基本特點它的設計思路是:先進后出&#xff0c;后進先出棧有兩端1 棧頂(top) &#xff1a;插入數據刪除數據都只能在這一端訪問也只能訪問棧頂2 棧底(bottom) : 棧底…

三、Spark 運行環境部署:全面掌握四種核心模式

作者&#xff1a;IvanCodes 日期&#xff1a;2025年7月25日 專欄&#xff1a;Spark教程 Apache Spark 作為統一的大數據分析引擎&#xff0c;以其高性能和靈活性著稱。要充分利用Spark的強大能力&#xff0c;首先需要根據不同的應用場景和資源環境&#xff0c;正確地部署其運行…

【Django】-2- 處理HTTP請求

一、request 請求 先理解&#xff1a;Request 是啥&#xff1f;用戶訪問你的網站時&#xff0c;會發一個 “請求包” &#x1f4e6; &#xff0c;里面裝著&#xff1a;想訪問啥路徑&#xff1f;用啥方法&#xff08;GET/POST 等&#xff09;&#xff1f;帶了啥頭信息&#xff0…

飛算 JavaAI:突破效率邊界的代碼智能構造平臺

飛算 JavaAI&#xff1a;突破效率邊界的代碼智能構造平臺 一、引言&#xff1a;數字化浪潮下的開發效率困局與破局路徑 當企業數字化轉型駛入深水區&#xff0c;軟件開發正面臨需求迭代頻次激增、人力成本高企、技術架構復雜化的多重挑戰。傳統開發模式中&#xff0c;從需求分…

國家科學技術獎答辯PPT案例_科技進步獎ppt制作_技術發明獎ppt設計美化_自然科學獎ppt模板 | WordinPPT

“國家科學技術獎”是在科學技術領域設立的最高榮譽&#xff0c;旨在獎勵在科學技術進步活動中做出突出貢獻的個人和組織&#xff0c;從而推動國家科學技術事業的發展&#xff0c;加快建設科技強國。科學技術獎是國內科技界的最高殿堂&#xff0c;是對做出杰出貢獻的科技工作者…

如何通過黑白棋盤進行定位配準融合?(前后安裝的兩個相機)

一.總結: 完整流程 &#xff1a;硬件準備 → 數據采集 → 空間統一 → 相機標定&#xff08;內參畸變&#xff09; → 外參求解 → 定位配準融合 → 校驗 → 生成映射表 → 上線remap驗證 我們場景流程 &#xff1a;硬件準備 → 數據采集 → 空間統一 → 定位配準融合 → …

【node】token的生成與解析配置

在用戶登錄成功之后為了記錄用戶的登錄狀態通常會將用戶信息編寫為一個token&#xff0c;通過解析token判斷用戶是否登錄。 token的生成 JSON Web Token&#xff08;JWT&#xff09; 是一種基于JSON的輕量級身份驗證和授權機制。它是一種開放標準&#xff08;RFC 7519&#xff…

yolo 、Pytorch (5)IOU

一、簡介 IOU的全稱為交并比&#xff08;Intersection over Union&#xff09;&#xff0c;是目標檢測中使用的一個概念&#xff0c;IoU計算的是“預測的邊框”和“真實的邊框”的交疊率&#xff0c;即它們的交集和并集的比值。最理想情況是完全重疊&#xff0c;即比值為1。 …

【銀河麒麟服務器系統】自定義ISO鏡像更新內核版本

自定義ISO鏡像更新內核版本 鏡像制作流程 環境 更新倉庫 準備新版本內核包 內核清單簡介 已下載軟件包版本 更新內核包 更新鏡像源 制作自動化鏡像 修改引導 修改UEFI引導 傳統引導 修改ks文件內容 打包鏡像 mkisofs參數說明 封裝鏡像命令 常見問題解決方案 鏡像制作流程 #merm…

JVM 調優中JVM的參數如何起到調優動作?具體案例,G1GC垃圾收集器參數調整建議

JVM調優參數 在JVM調優過程中&#xff0c;通過調整JVM參數可以優化Java應用程序的性能。不同的應用場景可能需要不同的調優策略和參數配置。下面將介紹幾個常見的調優場景以及相應的JVM參數設置&#xff0c;并給出具體案例說明。 1. 堆內存大小調整 問題描述&#xff1a;應用程…

TGD第十一篇:卷積神經網絡中的TGD特征

文章目錄一、直覺上重要的視覺特征二、視覺神經網絡首層試圖自主學習 TGD 算子權重2.1 AlexNet2.2 Vision Transformer2.3 MLPMixer三、針對直覺的驗證試驗3.1 小樣本集自然圖像分類任務3.2 小樣本集醫學圖像分割任務四、結語早在 2012 年&#xff0c;卷積神經網絡 AlexNet 就已…

【源力覺醒 創作者計劃】文心大模型開源:從封閉研發到生態共建的轉折點

前言 人工智能的浪潮在近幾年席卷全球&#xff0c;不僅顛覆了傳統技術路徑與行業習慣&#xff0c;更在大模型領域掀起了一場激烈的生態爭奪戰。自去年起&#xff0c;"百模大戰"的硝煙彌漫&#xff0c;微軟、谷歌、百度、阿里等科技巨頭紛紛入局&#xff0c;在大模型的…

思科 UCS Fabric Interconnect 和 UCS Manager 簡介

UCS Manager&#xff08;UCSM&#xff09;安裝在 Fabric Interconnect&#xff08;FI&#xff09;上&#xff0c;并且是UCS架構的集中管理平臺&#xff0c;允許你管理所有與計算、網絡和存儲相關的配置。1. UCS Manager 安裝位置UCS Manager 是在 UCS Fabric Interconnect&…

C語言結構體、位段、枚舉、聯合體

結構體&#xff1a;定義&#xff1a;結構體就是一堆值的集合初始化&#xff1a;#include<stdio.h> #include <stddef.h> struct S {char ch;int n; };int main() {struct S s1 { a, 5 };S s2{ b,6 };printf("s1 ch:%c , n:%d\n", s1.ch, s1.n);printf(&…

AI產品經理面試寶典第61天:AI產品體驗、數據安全與架構實戰解析

1. 如何提升 AI 產品的用戶體驗? 1.1 問:如何提升 AI 產品的用戶體驗? 答: 提升 AI 產品的用戶體驗可以從以下幾個方面入手: 可解釋性增強:AI模型的輸出往往較為“黑盒”,用戶難以理解其決策邏輯。通過可視化、自然語言解釋、關鍵特征展示等方式,增強用戶對AI決策過程…

以微服務為基礎搭建一套腳手架開始前的介紹

書接上回<java一個腳手架搭建-CSDN博客> 這個腳?架項?開發前&#xff0c;你要大概的了解一下這些東西&#xff1a; Java基礎、IDEA使?、Maven基礎 ? Linux基礎 ? Springboot/Spring Cloud 基礎 ? MySQL基礎 ? Redis基礎 ? RabbitMQ基礎 ? Docker基礎 ? Git基…

Excel接入deepseek

先進入deepseek官網&#xff1a;DeepSeek | 深度求索 點擊API開放平臺&#xff1a; 確保余額里有錢: 創建APIkey: 復制到.txt文件中儲存好 插入VBA代碼&#xff1a; Function OptimizeEbayTitle(originalTitle As String) As StringDim Prompt As StringPrompt "作為…