Golang goroutine 與 map 并發(fā)的采坑事件

goroutine 與 map 并發(fā)的采坑事件

1. goroutine 與map 的并發(fā)讀寫操作

在Go 1.6之前, 內(nèi)置的map類型是部分goroutine安全的,并發(fā)的讀沒有問題,并發(fā)的寫可能有問題。自go 1.6之后, 并發(fā)地讀寫map會報錯,這在一些知名的開源庫中都存在這個問題,所以go 1.9之前的解決方案是額外綁定一個鎖,封裝成一個新的struct或者單獨使用鎖都可以。

因為map為引用類型,所以即使函數(shù)傳值調(diào)用,參數(shù)副本依然指向映射m, 所以多個goroutine并發(fā)寫同一個映射m, 寫過多線程程序的同學(xué)都知道,對于共享變量,資源,并發(fā)讀寫會產(chǎn)生競爭的, 故共享資源遭到破壞


1. 有并發(fā)問題的map

官方的Why are map operations not defined to be atomic? ]已經(jīng)提到內(nèi)建的map不是線程(goroutine)安全的。

... and in those cases where it did, the map was probably part of some larger data structure or computation that was already synchronized.

我們來看一下代碼吧:

一個goroutine一直讀,一個goroutine一只寫同一個鍵值,即即使讀寫的鍵不相同,而且map也沒有"擴容"等操作,代碼還是會報錯。


func main() {

    m := make(map[int]int)
    go func() {
        for {
            _ = m[1]
        }
    }()
    go func() {
        for {
            m[2] = 2
        }
    }()
    select {}
}


然后你會發(fā)現(xiàn) 運行不起:

fatal error: concurrent map read and map write

有時候數(shù)據(jù)競爭不是很容易發(fā)現(xiàn),你可以輸入:

go run --race main.go

進行查看。


2. Go 1.9之前的解決方案

你可以用互斥鎖 sync.Mutex 也可以用讀寫鎖 sync.RWMutex(性能好些)。


var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}


讀數(shù)據(jù)的時候很方便的加鎖:


counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)


寫數(shù)據(jù)的時候:


counter.Lock()
counter.m["some_key"]++
counter.Unlock()

當然你也可以單獨使用 讀寫鎖進行讀寫加鎖操作,只是如果有多個的情況下就沒有嵌入結(jié)構(gòu)體那么方便操作了。


3. 終極解決方案 sync.Map

可以說,上面的解決方案相當簡潔,并且利用讀寫鎖而不是Mutex可以進一步減少讀寫的時候因為鎖帶來的性能。

  • Store
  • LoadOrStore
  • Load
  • Delete
  • Range

Store(key, value interface{})

存 key,value 存儲一個設(shè)置的鍵值。

LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)

返回鍵的現(xiàn)有值(如果存在),否則存儲并返回給定的值,如果是讀取則返回true,如果是存儲返回false。

Load(key interface{}) (value interface{}, ok bool)

讀取存儲在map中的值,如果沒有值,則返回nil。OK的結(jié)果表示是否在map中找到值。

Delete(key interface{})

刪除key,及其value

Range(f func(key, value interface{}) bool)

循環(huán)讀取map中的值.遍歷所有的key,value


package main
 
import (
    "fmt"
    "sync"
)
 
func main() {
    var m sync.Map
 
    //Store
    m.Store(1,"a")
    m.Store(2,"b")
 
    //LoadOrStore
    //若key不存在,則存入key和value,返回false和輸入的value
    v,ok := m.LoadOrStore("1","aaa")
    fmt.Println(ok,v) //false aaa
 
    //若key已存在,則返回true和key對應(yīng)的value,不會修改原來的value
    v,ok = m.LoadOrStore(1,"aaa")
    fmt.Println(ok,v) //false aaa
 
    //Load
    v,ok = m.Load(1)
    if ok{
        fmt.Println("it's an existing key,value is ",v)
    } else {
        fmt.Println("it's an unknown key")
    }
 
    //Range
    //遍歷sync.Map, 要求輸入一個func作為參數(shù)
    f := func(k, v interface{}) bool {
        //這個函數(shù)的入?yún)?、出參的類型都已?jīng)固定,不能修改
        //可以在函數(shù)體內(nèi)編寫自己的代碼,調(diào)用map中的k,v
 
            fmt.Println(k,v)
            return true
        }
    m.Range(f)
 
    //Delete
    m.Delete(1)
    fmt.Println(m.Load(1))
 
}

關(guān)于 其源碼分析 可參考此文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容