最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Go語(yǔ)言如何解決map并發(fā)安全問(wèn)題

 更新時(shí)間:2024年04月11日 11:30:21   作者:shark_chili  
常說(shuō)go語(yǔ)言是一門并發(fā)友好的語(yǔ)言,對(duì)于并發(fā)操作總會(huì)在編譯期完成安全檢查,所以這篇文章我們就來(lái)聊聊go語(yǔ)言是如何解決map這個(gè)數(shù)據(jù)結(jié)構(gòu)的線程安全問(wèn)題吧

常說(shuō)go語(yǔ)言是一門并發(fā)友好的語(yǔ)言,對(duì)于并發(fā)操作總會(huì)在編譯期完成安全檢查,所以這篇文章我們就來(lái)聊聊go語(yǔ)言是如何解決map這個(gè)數(shù)據(jù)結(jié)構(gòu)的線程安全問(wèn)題。

詳解map中的并發(fā)安全問(wèn)題

問(wèn)題復(fù)現(xiàn)

我們通過(guò)字面量的方式創(chuàng)建一個(gè)map集合,然后開(kāi)啟兩個(gè)協(xié)程,其中協(xié)程1負(fù)責(zé)寫,協(xié)程2負(fù)責(zé)讀:

func main() {
 //創(chuàng)建map
 m := make(map[int]string)
 //聲明一個(gè)長(zhǎng)度為2的倒計(jì)時(shí)門閂
 var wg sync.WaitGroup
 wg.Add(2)

 //協(xié)程1寫
 go func() {
  for true {
   m[0] = "xiaoming"
  }
  wg.Done()
 }()

 //協(xié)程2讀
 go func() {
  for true {
   _ = m[0]
  }
  wg.Done()

 }()

 wg.Wait()
 fmt.Println("結(jié)束")
}

在完成編譯后嘗試運(yùn)行

fatal error: concurrent map read and map write 

并發(fā)操作失敗的原因

我們直接假設(shè)一個(gè)場(chǎng)景,協(xié)程并發(fā)場(chǎng)景下當(dāng)前的map處于擴(kuò)容狀態(tài),假設(shè)我們的協(xié)程1修改了key-111對(duì)應(yīng)的元素觸發(fā)漸進(jìn)式驅(qū)逐操作,使得key-111移動(dòng)到新桶上,結(jié)果協(xié)程2緊隨其后嘗試讀取key-111對(duì)應(yīng)的元素,結(jié)果得到nil,由此引發(fā)了協(xié)程安全問(wèn)題:

上鎖解決并發(fā)安全問(wèn)題

Java一樣,go語(yǔ)言也有自己的鎖sync.Mutex,我們?cè)趨f(xié)程進(jìn)行map操作前后進(jìn)行上鎖和釋放的鎖的操作,確保單位時(shí)間內(nèi)只有一個(gè)協(xié)程在操作map,從而實(shí)現(xiàn)協(xié)程安全,因?yàn)檫@種鎖是排他鎖,這使得協(xié)程的并發(fā)特性得不到發(fā)揮:

var mu sync.Mutex


func main() {
 //創(chuàng)建map
 m := make(map[int]string)
 
 var wg sync.WaitGroup
 wg.Add(2)

 //協(xié)程1上鎖后寫
 go func() {

  for true {
   mu.Lock()
   m[0] = "xiaoming"
   mu.Unlock()
  }
  wg.Done()
 }()

 //協(xié)程2上鎖后讀
 go func() {
  for true {
   mu.Lock()
   _ = m[0]
   mu.Unlock()
  }
  wg.Done()

 }()

 wg.Wait()
 fmt.Println("結(jié)束")
}

使用自帶的sync.map進(jìn)行并發(fā)讀寫

好在go語(yǔ)言為我們提供的現(xiàn)成的"輪子",即sync.Map,我們直接通過(guò)其內(nèi)置函數(shù)storeload即可實(shí)現(xiàn)并發(fā)讀寫還能保證協(xié)程安全:

func main() {
 //創(chuàng)建sync.Map
 var m sync.Map
 
 
 var wg sync.WaitGroup
 wg.Add(2)

 //協(xié)程1并發(fā)寫
 go func() {

  for true {
   m.Store(1, "xiaoming")
  }
  wg.Done()
 }()

 //協(xié)程2并發(fā)讀
 go func() {
  for true {
   m.Load(1)
  }
  wg.Done()

 }()

 wg.Wait()
 fmt.Println("結(jié)束")
}

詳解sync.map并發(fā)操作流程

常規(guī)sync.map并發(fā)讀或?qū)?/h3>

sync.map會(huì)有一個(gè)readdirty指針,指向不同的key數(shù)組,但是這些key對(duì)應(yīng)的value指針都是一樣的,這意味著這個(gè)map不同桶的相同key共享同一套value。

進(jìn)行并發(fā)讀取或者寫的時(shí)候,首先拿到一個(gè)原子類型的read指針,通過(guò)CAS嘗試修改元素值,如果成功則直接返回,就如下圖所示,我們的協(xié)程通過(guò)CAS完成原子指針數(shù)值讀取之后,直接操作read指針?biāo)赶虻?code>map元素,通過(guò)key定位到value完成修改后直接返回。

sync.map修改或追加

接下來(lái)再說(shuō)說(shuō)另一種情況,假設(shè)我們追加一個(gè)元素key-24,通過(guò)read指針進(jìn)行讀取發(fā)現(xiàn)找不到,這就意味當(dāng)前元素不存在或者在dirty指針指向的map下,所以我們會(huì)先上重量級(jí)鎖,然后再上一次read鎖。 分別到readdirty指針上查詢對(duì)應(yīng)key,進(jìn)行如下三部曲:

  • 如果在read發(fā)現(xiàn)則修改。
  • 如果在dirty下發(fā)現(xiàn)則修改。
  • 都沒(méi)發(fā)現(xiàn)則說(shuō)明要追加了,則將amended設(shè)置為true說(shuō)明當(dāng)前map臟了,嘗試將元素追加到dirty指針管理的map下。

這里需要補(bǔ)充一句,通過(guò)amended可知當(dāng)前map是否處于臟寫狀態(tài),如果這個(gè)標(biāo)志為true,后續(xù)每次讀寫未命中都會(huì)對(duì)misses進(jìn)行自增操作,一旦未命中數(shù)達(dá)到dirty數(shù)組的長(zhǎng)度(大抵是想表達(dá)所有未命中的都在dirty數(shù)組上)閾值就會(huì)進(jìn)行一次dirty提升,將dirty的key提升為read指針指向的數(shù)組,確保提升后續(xù)并發(fā)讀寫的命中率:

sync.map并發(fā)刪除

并發(fā)刪除也和上述并發(fā)讀寫差不多,都是先通過(guò)read指針嘗試是否成功,若不成功則鎖主mutex到dirty進(jìn)行刪除,所以這里就不多贅述了。

sync.map源碼解析

sync.map內(nèi)存結(jié)構(gòu)

通過(guò)上文我們了解了sync.map的基本操作,這里我們?cè)倩剡^(guò)頭看看sync.map的數(shù)據(jù)結(jié)構(gòu),即重量級(jí)鎖mu Mutex,

type Map struct {
 //重量級(jí)鎖
 mu Mutex
 //read指針,指向一個(gè)不可變的key數(shù)組
 read atomic.Pointer[readOnly]

 //dirty 指針指向可以進(jìn)行追加操作的key數(shù)組
 dirty map[any]*entry

 //當(dāng)前map讀寫未命中次數(shù)
 misses int
}

sync.Map并發(fā)寫源碼

并發(fā)寫底層本質(zhì)是調(diào)用Swap進(jìn)行追加或者修改:

func (m *Map) Store(key, value any) {
 _, _ = m.Swap(key, value)
}

步入swap底層即可看到上文圖解的操作,這里我們給出核心源碼,讀者可自行參閱:

func (m *Map) Swap(key, value any) (previous any, loaded bool) {
 //上read嘗試修改
 read := m.loadReadOnly()
 if e, ok := read.m[key]; ok {
  if v, ok := e.trySwap(&value); ok {
   if v == nil {
    return nil, false
   }
   return *v, true
  }
 }
 //上重量級(jí)鎖和read原子指針加載進(jìn)行修改
 m.mu.Lock()
 read = m.loadReadOnly()
 if e, ok := read.m[key]; ok {
  if e.unexpungeLocked() {
   
   m.dirty[key] = e
  }
  if v := e.swapLocked(&value); v != nil {
   loaded = true
   previous = *v
  }
 } else if e, ok := m.dirty[key]; ok { //如果在dirty數(shù)組發(fā)現(xiàn)則上swap鎖進(jìn)行修改
  if v := e.swapLocked(&value); v != nil {
   loaded = true
   previous = *v
  }
 } else {//上述情況都不符合則將amended 標(biāo)記為true后進(jìn)行追加
  if !read.amended {
   
   m.dirtyLocked()
   m.read.Store(&readOnly{m: read.m, amended: true})
  }
  m.dirty[key] = newEntry(value)
 }
 //解鎖返回
 m.mu.Unlock()
 return previous, loaded
}

sync.Map讀取

對(duì)應(yīng)的讀取源碼即加載read原子變量后嘗試到read指針下讀取,若讀取不到則增加未命中數(shù)到dirty指針下讀?。?/p>

func (m *Map) Load(key any) (value any, ok bool) {
 //加載讀原子變量
 read := m.loadReadOnly()
 //嘗試在read指針下讀取
 e, ok := read.m[key]
 //沒(méi)讀取到上mutex鎖到dirty下讀取,若發(fā)現(xiàn)則更新未命中數(shù)后返回結(jié)果
 if !ok && read.amended {
  m.mu.Lock()
  
  read = m.loadReadOnly()
  e, ok = read.m[key]
  if !ok && read.amended {
   e, ok = m.dirty[key]
   //更新未命中數(shù)
   m.missLocked()
  }
  m.mu.Unlock()
 }
 if !ok {
  return nil, false
 }
 return e.load()
}

sync.Map刪除

刪除步驟也和前面幾種操作差不多,這里就不多贅述了,讀者可參考筆者核心注釋了解流程:

func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
 //上讀鎖定位元素
 read := m.loadReadOnly()
 
 e, ok := read.m[key]
 //為命中則上重量級(jí)鎖到read和dirty下再次查找,找到了則刪除,若是在dirty下找到還需要額外更新一下未命中數(shù)
 if !ok && read.amended {
  m.mu.Lock()
  read = m.loadReadOnly()
  e, ok = read.m[key]
  if !ok && read.amended {
   e, ok = m.dirty[key]
   delete(m.dirty, key)
   //自增一次未命中數(shù)
   m.missLocked()
  }
  m.mu.Unlock()
 }
 if ok {
  return e.delete()
 }
 return nil, false
}

// Delete deletes the value for a key.
func (m *Map) Delete(key any) {
 m.LoadAndDelete(key)
}

以上就是詳解Go語(yǔ)言如何解決map并發(fā)安全問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于Go解決map并發(fā)安全的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go字符串切片操作str1[:index]的使用

    Go字符串切片操作str1[:index]的使用

    Go字符串切片str1[:index]從起始位置0到index-1截取,不復(fù)制數(shù)據(jù),利用字符串不可變性和共享內(nèi)存機(jī)制提升性能,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-06-06
  • go語(yǔ)言實(shí)現(xiàn)處理表單輸入

    go語(yǔ)言實(shí)現(xiàn)處理表單輸入

    本文給大家分享的是一個(gè)使用go語(yǔ)言實(shí)現(xiàn)處理表單輸入的實(shí)例代碼,非常的簡(jiǎn)單,僅僅是實(shí)現(xiàn)了用戶名密碼的驗(yàn)證,有需要的小伙伴可以自由擴(kuò)展下。
    2015-03-03
  • go語(yǔ)言實(shí)現(xiàn)全排列的示例代碼

    go語(yǔ)言實(shí)現(xiàn)全排列的示例代碼

    本文主要介紹了go語(yǔ)言實(shí)現(xiàn)全排列的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • 詳解Go語(yǔ)言單元測(cè)試中如何解決MySQL存儲(chǔ)依賴問(wèn)題

    詳解Go語(yǔ)言單元測(cè)試中如何解決MySQL存儲(chǔ)依賴問(wèn)題

    MySQL?存儲(chǔ)就是一個(gè)非常常見(jiàn)的外部依賴,這篇文章主要來(lái)和大家一起探討在?Go?語(yǔ)言中編寫單元測(cè)試時(shí),如何解決?MySQL?存儲(chǔ)依賴,需要的可以參考一下
    2023-07-07
  • Go打包靜態(tài)文件的兩種方式

    Go打包靜態(tài)文件的兩種方式

    使用 Go 開(kāi)發(fā)應(yīng)用的時(shí)候,有時(shí)會(huì)遇到需要讀取靜態(tài)資源的情況,如果不打包處理這種靜態(tài)文件:發(fā)布單獨(dú)掛載這種靜態(tài)文件相對(duì)比較麻煩,就有人會(huì)想辦法把靜態(tài)資源文件打包進(jìn) Go 的程序文件中,下面介紹兩種打包方式:go-bindata、go:embed,需要的朋友可以參考下
    2024-04-04
  • Go語(yǔ)言Select chan用法小結(jié)

    Go語(yǔ)言Select chan用法小結(jié)

    select語(yǔ)句是Go語(yǔ)言中用于處理多個(gè)通道操作的關(guān)鍵字,它允許你在多個(gè)通道上進(jìn)行非阻塞的選擇操作,本文就詳細(xì)介紹一下如何使用,感興趣的可以了解一下
    2023-09-09
  • go內(nèi)置函數(shù)copy()的具體使用

    go內(nèi)置函數(shù)copy()的具體使用

    當(dāng)我們?cè)贕o語(yǔ)言中需要將一個(gè)切片的內(nèi)容復(fù)制到另一個(gè)切片時(shí),可以使用內(nèi)置的copy()函數(shù),本文就介紹了go內(nèi)置函數(shù)copy()的具體使用,感興趣的可以了解一下
    2023-08-08
  • go+react實(shí)現(xiàn)遠(yuǎn)程vCenter虛擬機(jī)管理終端方式

    go+react實(shí)現(xiàn)遠(yuǎn)程vCenter虛擬機(jī)管理終端方式

    基于Go和React實(shí)現(xiàn)遠(yuǎn)程vSphere vcenter虛擬機(jī)終端console頁(yè)面,提供與vcenter管理中的LaunchWebConsole相同的功能,項(xiàng)目包括前端、后端配置,以及vCenter宿主機(jī)的Nginx代理設(shè)置
    2026-04-04
  • Go?語(yǔ)言入門之Go?計(jì)時(shí)器介紹

    Go?語(yǔ)言入門之Go?計(jì)時(shí)器介紹

    這篇文章主要介紹了Go?語(yǔ)言入門之Go?計(jì)時(shí)器,文章基于GO語(yǔ)言的相關(guān)資料展開(kāi)對(duì)其中計(jì)時(shí)器的詳細(xì)內(nèi)容。具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-05-05
  • go性能分析工具pprof的用途及使用詳解

    go性能分析工具pprof的用途及使用詳解

    剛開(kāi)始接觸go就遇到了一個(gè)內(nèi)存問(wèn)題,在進(jìn)行內(nèi)存分析的時(shí)候發(fā)現(xiàn)了一下比較好的工具,在此留下記錄,下面這篇文章主要給大家介紹了關(guān)于go性能分析工具pprof的用途及使用的相關(guān)資料,需要的朋友可以參考下
    2023-01-01

最新評(píng)論

光山县| 龙里县| 含山县| 肃南| 合作市| 兴山县| 嘉峪关市| 晋城| 福州市| 孟津县| 西宁市| 敦煌市| 大宁县| 墨竹工卡县| 长寿区| 迭部县| 云梦县| 资兴市| 古田县| 红原县| 岑巩县| 怀化市| 古交市| 且末县| 来安县| 连云港市| 读书| 航空| 比如县| 新建县| 清水县| 图们市| 安远县| 永州市| 巩留县| 濉溪县| 玛沁县| 敦化市| 柘城县| 韩城市| 定兴县|