成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

為什么 Go Map 和 Slice 是非線性安全的?

開發 后端
初入 Go 語言的大門,有不少的小伙伴會快速的 3 天精通 Go,5 天上手項目,14 天上線業務迭代,21 天排查、定位問題,順帶捎個反省報告。

[[399435]]

本文轉載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉載本文請聯系腦子進煎魚了公眾號。

大家好,我是煎魚。

初入 Go 語言的大門,有不少的小伙伴會快速的 3 天精通 Go,5 天上手項目,14 天上線業務迭代,21 天排查、定位問題,順帶捎個反省報告。

其中最常見的初級錯誤,Go 面試較最愛問的問題之一:

為什么在 Go 語言里,map 和 slice 不支持并發讀寫,也就是是非線性安全的,為什么不支持?

見招拆招后,緊接著就會開始討論如何讓他們倆 ”冤家“ 支持并發讀寫?

今天我們這篇文章就來理一理,了解其前因后果,一起吸魚學懂 Go 語言。

非線性安全的例子

slice

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

如下:

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

輸出結果:

  1. // 第一次執行 
  2. 進了 5790 只煎魚 
  3. // 第二次執行 
  4. 進了 7370 只煎魚 
  5. // 第三次執行 
  6. 進了 6792 只煎魚 

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

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

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

map

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

如下:

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

輸出結果:

  1. fatal error: concurrent map writes 
  2.  
  3. goroutine 18 [running]: 
  4. runtime.throw(0x10cb861, 0x15) 
  5.         /usr/local/Cellar/go/1.16.2/libexec/src/runtime/panic.go:1117 +0x72 fp=0xc00002e738 sp=0xc00002e708 pc=0x1032472 
  6. runtime.mapassign_faststr(0x10b3360, 0xc0000a2180, 0x10c91da, 0x6, 0x0) 
  7.         /usr/local/Cellar/go/1.16.2/libexec/src/runtime/map_faststr.go:211 +0x3f1 fp=0xc00002e7a0 sp=0xc00002e738 pc=0x1011a71 
  8. main.main.func1(0xc0000a2180) 
  9.         /Users/eddycjy/go-application/awesomeProject/main.go:9 +0x4c fp=0xc00002e7d8 sp=0xc00002e7a0 pc=0x10a474c 
  10. runtime.goexit() 
  11.         /usr/local/Cellar/go/1.16.2/libexec/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc00002e7e0 sp=0xc00002e7d8 pc=0x1063fe1 
  12. created by main.main 
  13.         /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 中提供了一種簡單又便利的方式來實現:

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

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

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

  1. counter.RLock() 
  2. n := counter.m["煎魚"
  3. counter.RUnlock() 
  4. fmt.Println("煎魚:", n) 

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

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

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

sync.Map

前言

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

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

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

具體介紹

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

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

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

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

提供了以下常用方法:

  1. func (m *Map) Delete(key interface{}) 
  2. func (m *Map) Load(key interface{}) (value interface{}, ok bool) 
  3. func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) 
  4. func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) 
  5. func (m *Map) Range(f func(key, value interface{}) bool) 
  6. func (m *Map) Store(key, value interface{}) 
  • Delete:刪除某一個鍵的值。
  • Load:返回存儲在 map 中的鍵的值,如果沒有值,則返回 nil。ok 結果表示是否在 map 中找到了值。
  • LoadAndDelete:刪除一個鍵的值,如果有的話返回之前的值。
  • LoadOrStore:如果存在的話,則返回鍵的現有值。否則,它存儲并返回給定的值。如果值被加載,加載的結果為 true,如果被存儲,則為 false。
  • Range:遞歸調用,對 map 中存在的每個鍵和值依次調用閉包函數 f。如果 f 返回 false 就停止迭代。
  • Store:存儲并設置一個鍵的值。

實際運行例子如下:

  1. var m sync.Map 
  2.  
  3. func main() { 
  4.  //寫入 
  5.  data := []string{"煎魚""咸魚""烤魚""蒸魚"
  6.  for i := 0; i < 4; i++ { 
  7.   go func(i int) { 
  8.    m.Store(i, data[i]) 
  9.   }(i) 
  10.  } 
  11.  time.Sleep(time.Second
  12.  
  13.  //讀取 
  14.  v, ok := m.Load(0) 
  15.  fmt.Printf("Load: %v, %v\n", v, ok) 
  16.  
  17.  //刪除 
  18.  m.Delete(1) 
  19.  
  20.  //讀或寫 
  21.  v, ok = m.LoadOrStore(1, "吸魚"
  22.  fmt.Printf("LoadOrStore: %v, %v\n", v, ok) 
  23.  
  24.  //遍歷 
  25.  m.Range(func(key, value interface{}) bool { 
  26.   fmt.Printf("Range: %v, %v\n"key, value) 
  27.   return true 
  28.  }) 

輸出結果:

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

為什么不支持

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

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

原因如下(via @go faq):

  • 典型使用場景:map 的典型使用場景是不需要從多個 goroutine 中進行安全訪問。
  • 非典型場景(需要原子操作):map 可能是一些更大的數據結構或已經同步的計算的一部分。
  • 性能場景考慮:若是只是為少數程序增加安全性,導致 map 所有的操作都要處理 mutex,將會降低大多數程序的性能。

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

總結

在今天這篇文章中,我們針對 Go 語言中的 map 和 slice 進行了基本的介紹,也對不支持并發讀者的場景進行了模擬展示。

同時也針對業內常見的支持并發讀寫的方式進行了講述,最后分析了不支持的原因,讓我們對整個前因后果有了一個完整的了解。

 

 

責任編輯:武曉燕 來源: 腦子進煎魚了
相關推薦

2012-06-15 09:56:40

2024-09-03 09:45:36

2022-02-09 16:02:26

Go 語言ArraySlice

2022-03-28 11:51:00

深度學習機器學習模型

2021-07-08 23:53:44

Go語言拷貝

2022-01-10 23:54:56

GoMap并發

2024-01-01 08:10:40

Go語言map

2024-01-05 08:45:35

Go語言map

2023-05-15 08:01:16

Go語言

2022-10-10 11:37:14

Gomap內存

2020-04-07 16:12:56

Go編程語言開發

2020-02-27 21:03:30

調度器架構效率

2021-12-09 10:51:47

Go繼承

2023-11-28 11:44:54

Go切片

2023-11-21 15:46:13

Go內存泄漏

2015-07-13 10:27:40

GoRust競爭者

2023-11-20 22:26:51

Go開發

2024-07-08 00:01:00

GPM模型調度器

2017-08-31 11:28:47

Slice底層實現

2018-06-21 08:50:53

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美一级小视频 | 欧美日韩一区二区三区四区 | 伊人在线| 国产成人免费网站 | av国产精品毛片一区二区小说 | 最新国产精品 | 亚洲免费精品 | 黄色av免费网站 | 三区四区在线观看 | 久草在线在线精品观看 | 99精品免费视频 | 少妇无套高潮一二三区 | 日韩精品久久久 | 日韩av手机在线观看 | 激情av网站 | 一本一道久久a久久精品综合蜜臀 | 国产乱一区二区三区视频 | 在线视频国产一区 | 在线观看免费观看在线91 | 亚洲精品中文字幕在线观看 | 一级片av | 久久久激情视频 | 国产精品美女视频 | 国产精品久久久久久吹潮 | 精品1区2区 | 欧美中文在线 | 久草资源| 99久久精品免费看国产四区 | 天天曰天天曰 | www.99精品| 国产日韩欧美激情 | 91精品国产乱码久久蜜臀 | 少妇特黄a一区二区三区88av | 羞羞视频网站在线观看 | 福利片在线| 欧美在线视频免费 | 一级毛片观看 | 毛片一级片 | 韩国主播午夜大尺度福利 | 国产精品亚洲成在人线 | 可以在线看的黄色网址 |