中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解Go Map和Slice屬于非線性安全

發布時間:2021-10-09 15:44:33 來源:億速云 閱讀:169 作者:iii 欄目:編程語言

本篇內容主要講解“如何理解Go Map和Slice屬于非線性安全”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“如何理解Go Map和Slice屬于非線性安全”吧!

如何理解Go Map和Slice屬于非線性安全

非線性安全的例子

slice

我們使用多個 goroutine 對類型為 slice 的變量進行操作,看看結果會變的怎么樣。

如下:

func main() {  var s []string  for i := 0; i < 9999; i++ {   go func() {    s = append(s, "腦子進煎魚了")   }()  }   fmt.Printf("進了 %d 只煎魚", len(s)) }

輸出結果:

// 第一次執行 進了 5790 只煎魚 // 第二次執行 進了 7370 只煎魚 // 第三次執行 進了 6792 只煎魚

你會發現無論你執行多少次,每次輸出的值大概率都不會一樣。也就是追加進 slice 的值,出現了覆蓋的情況。

因此在循環中所追加的數量,與最終的值并不相等。且這種情況,是不會報錯的,是一個出現率不算高的隱式問題。

這個產生的主要原因是程序邏輯本身就有問題,同時讀取到相同索引位,自然也就會產生覆蓋的寫入了。

map

同樣針對 map 也如法炮制一下。重復針對類型為 map 的變量進行寫入。

如下:

func main() {  s := make(map[string]string)  for i := 0; i < 99; i++ {   go func() {    s["煎魚"] = "吸魚"   }()  }   fmt.Printf("進了 %d 只煎魚", len(s)) }

輸出結果:

fatal error: concurrent map writes  goroutine 18 [running]: runtime.throw(0x10cb861, 0x15)         /usr/local/Cellar/go/1.16.2/libexec/src/runtime/panic.go:1117 +0x72 fp=0xc00002e738 sp=0xc00002e708 pc=0x1032472 runtime.mapassign_faststr(0x10b3360, 0xc0000a2180, 0x10c91da, 0x6, 0x0)         /usr/local/Cellar/go/1.16.2/libexec/src/runtime/map_faststr.go:211 +0x3f1 fp=0xc00002e7a0 sp=0xc00002e738 pc=0x1011a71 main.main.func1(0xc0000a2180)         /Users/eddycjy/go-application/awesomeProject/main.go:9 +0x4c fp=0xc00002e7d8 sp=0xc00002e7a0 pc=0x10a474c runtime.goexit()         /usr/local/Cellar/go/1.16.2/libexec/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc00002e7e0 sp=0xc00002e7d8 pc=0x1063fe1 created by main.main         /Users/eddycjy/go-application/awesomeProject/main.go:8 +0x55

好家伙,程序運行會直接報錯。并且是 Go 源碼調用 throw 方法所導致的致命錯誤,也就是說 Go 進程會中斷。

不得不說,這個并發寫 map 導致的 fatal error: concurrent map writes  錯誤提示。我有一個朋友,已經看過少說幾十次了,不同組,不同人...

是個日經的隱式問題。

如何支持并發讀寫

對 map 上鎖

實際上我們仍然存在并發讀寫 map 的訴求(程序邏輯決定),因為 Go 語言中的 goroutine 實在是太方便了。

像是一般寫爬蟲任務時,基本會用到多個 goroutine,獲取到數據后再寫入到 map 或者 slice 中去。

Go 官方在 Go maps in action 中提供了一種簡單又便利的方式來實現:

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

這條語句聲明了一個變量,它是一個匿名結構(struct)體,包含一個原生和一個嵌入讀寫鎖 sync.RWMutex。

要想從變量中中讀出數據,則調用讀鎖:

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

要往變量中寫數據,則調用寫鎖:

counter.Lock() counter.m["煎魚"]++ counter.Unlock()

這就是一個最常見的 Map 支持并發讀寫的方式了。

sync.Map

前言

雖然有了 Map+Mutex 的極簡方案,但是也仍然存在一定問題。那就是在 map  的數據量非常大時,只有一把鎖(Mutex)就非常可怕了,一把鎖會導致大量的爭奪鎖,導致各種沖突和性能低下。

常見的解決方案是分片化,將一個大 map 分成多個區間,各區間使用多個鎖,這樣子鎖的粒度就大大降低了。不過該方案實現起來很復雜,很容易出錯。因此 Go  團隊到比較為止暫無推薦,而是采取了其他方案。

該方案就是在 Go1.9 起支持的 sync.Map,其支持并發讀寫 map,起到一個補充的作用。

具體介紹

Go 語言的 sync.Map 支持并發讀寫 map,采取了 “空間換時間” 的機制,冗余了兩個數據結構,分別是:read 和  dirty,減少加鎖對性能的影響:

type Map struct {  mu Mutex  read atomic.Value // readOnly  dirty map[interface{}]*entry  misses int }

其是專門為 append-only 場景設計的,也就是適合讀多寫少的場景。這是他的優點之一。

若出現寫多/并發多的場景,會導致 read map 緩存失效,需要加鎖,沖突變多,性能急劇下降。這是他的重大缺點。

提供了以下常用方法:

func (m *Map) Delete(key interface{}) func (m *Map) Load(key interface{}) (value interface{}, ok bool) func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) func (m *Map) Range(f func(key, value interface{}) bool) func (m *Map) Store(key, value interface{})
  • Delete:刪除某一個鍵的值。

  • Load:返回存儲在 map 中的鍵的值,如果沒有值,則返回 nil。ok 結果表示是否在 map 中找到了值。

  • LoadAndDelete:刪除一個鍵的值,如果有的話返回之前的值。

  • LoadOrStore:如果存在的話,則返回鍵的現有值。否則,它存儲并返回給定的值。如果值被加載,加載的結果為 true,如果被存儲,則為  false。

  • Range:遞歸調用,對 map 中存在的每個鍵和值依次調用閉包函數 f。如果 f 返回 false 就停止迭代。

  • Store:存儲并設置一個鍵的值。

實際運行例子如下:

var m sync.Map  func main() {  //寫入  data := []string{"煎魚", "咸魚", "烤魚", "蒸魚"}  for i := 0; i < 4; i++ {   go func(i int) {    m.Store(i, data[i])   }(i)  }  time.Sleep(time.Second)   //讀取  v, ok := m.Load(0)  fmt.Printf("Load: %v, %v\n", v, ok)   //刪除  m.Delete(1)   //讀或寫  v, ok = m.LoadOrStore(1, "吸魚")  fmt.Printf("LoadOrStore: %v, %v\n", v, ok)   //遍歷  m.Range(func(key, value interface{}) bool {   fmt.Printf("Range: %v, %v\n", key, value)   return true  }) }

輸出結果:

Load: 煎魚, true LoadOrStore: 吸魚, false Range: 0, 煎魚 Range: 1, 吸魚 Range: 3, 蒸魚 Range: 2, 烤魚

為什么不支持

Go Slice 的話,主要還是索引位覆寫問題,這個就不需要糾結了,勢必是程序邏輯在編寫上有明顯缺陷,自行改之就好。

但 Go map 就不大一樣了,很多人以為是默認支持的,一個不小心就翻車,這么的常見。那憑什么 Go  官方還不支持,難不成太復雜了,性能太差了,到底是為什么?

原因如下(via @go faq):

  • 典型使用場景:map 的典型使用場景是不需要從多個 goroutine 中進行安全訪問。

  • 非典型場景(需要原子操作):map 可能是一些更大的數據結構或已經同步的計算的一部分。

  • 性能場景考慮:若是只是為少數程序增加安全性,導致 map 所有的操作都要處理 mutex,將會降低大多數程序的性能。

匯總來講,就是 Go 官方在經過了長時間的討論后,認為 Go map  更應適配典型使用場景,而不是為了小部分情況,導致大部分程序付出代價(性能),決定了不支持。

到此,相信大家對“如何理解Go Map和Slice屬于非線性安全”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

滁州市| 松原市| 桐柏县| 阿尔山市| 赤峰市| 沭阳县| 安吉县| 赣榆县| 天柱县| 景德镇市| 龙川县| 建阳市| 红桥区| 自治县| 益阳市| 涞水县| 南阳市| 四会市| 米易县| 久治县| 锦州市| 黑龙江省| 金溪县| 北流市| 贵港市| 襄城县| 兰考县| 开封市| 穆棱市| 城步| 瓮安县| 乳山市| 扶沟县| 建平县| 新沂市| 应用必备| 卓尼县| 延津县| 南涧| 砚山县| 塔河县|