Golang 解決 Map 併發讀寫安全問題

一、錯誤案例

package main

import (
	"fmt"
	"time"
)

var TestMap map[string]string

func init() {
	TestMap = make(map[string]string, 1)
}

func main() {
	for i := 0; i < 1000; i++ {
		go Write("aaa")
		go Read("aaa")
		go Write("bbb")
		go Read("bbb")
	}
	time.Sleep(5 * time.Second)
}

func Read(key string) {
	fmt.Println(TestMap[key])
}

func Write(key string) {
	TestMap[key] = key
}

上面代碼執行大概率出現報錯:fatal error: concurrent map writes

二、問題分析

網上關於 golang 編程中 map 併發讀寫相關的資料很多,但總是都說成 併發讀寫 造成上面的錯誤,到底是 併發讀 還是 併發寫 造成的,這個很多資料都沒有說明。

我們把上面的案例分別在循環中註釋 Read 和 Write 函數的調用,分別測試 併發讀 和 併發寫;

循環次數分別測試了 100、1 w、100 w 次,併發讀操作絕對不會報上面的錯,而併發寫基本都會報錯。

因此,這個錯誤主要原因是:map 併發寫。

三、問題原因

爲什麼 map 併發寫會導致這個錯誤? 網絡上的相關文章也大都有說明。

因爲 map 變量爲 指針類型變量,併發寫時,多個協程同時操作一個內存,類似於多線程操作同一個資源會發生競爭關係,共享資源會遭到破壞,因此golang 出於安全的考慮,拋出致命錯誤:fatal error: concurrent map writes。

四、解決方案

網上各路資料解決方案較多,主要思路是通過加鎖保證每個協程同步操作內存。

github 上找到一個 concurrentMap 包,案例代碼修改如下:

package main

import (
	"fmt"
	cmap "github.com/orcaman/concurrent-map"
	"time"
)

var TestMap cmap.ConcurrentMap

func init() {
	TestMap = cmap.New()
}

func main() {
	for i := 0; i < 100; i++ {
		go Write("aaa", "111")
		go Read("aaa")
		go Write("bbb", "222")
		go Read("bbb")
	}
	time.Sleep(5 * time.Second)
}

func Read(key string) {
	if v, ok := TestMap.Get(key); ok {
		fmt.Printf("鍵值爲 %s 的值爲:%s", key, v)
	} else {
		fmt.Printf("鍵值不存在")
	}
}

func Write(key string, value string) {
	TestMap.Set(key, value)
}

五、思考總結

因爲我是以 PHP 打開的編程世界,PHP 語言只有單線程,且不涉及指針操作,變量類型也是弱變量,以 PHP 編程思維剛開始接觸 Golang 時還比較容易上手,但越往後,語言的特性區別就體現得越來越明顯,思維轉變就越來越大,對我來說是打開了一個新世界。

像本文出現的錯誤案例,也是因爲自己沒有多線程編程的思維基礎,導致對這種問題不敏感,還是花了蠻多時間理解的。希望對和我有相似學習路線的朋友提供到一些幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章