起因
從幣安實時拉取交易對的數據,這里使用了 map
,用於存放每個交易對的最新價格,由於 map
並不是並發安全的所以加了讀寫鎖。
但系統有時候還是會發生 fatal error: concurrent map iteration and map write
錯誤
使用代碼如下:
type BinanceSymbolPrice struct {
Item map[string]SymbolPrice
Lock sync.RWMutex
}
func NewSymbolPrice() *BinanceSymbolPrice {
info := &BinanceSymbolPrice{
Item: make(map[string]SymbolPrice),
}
return info
}
/**
* 現貨最新價格行情操作
*/
func (bst *BinanceSymbolPrice) GetAllSymbolPriceInfo() map[string]SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
return bst.Item
}
func (bst *BinanceSymbolPrice) GetSymbolPriceInfo(symbol string) SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
info, exist := bst.Item[symbol]
if !exist {
return SymbolPrice{}
}
return info
}
func (bst *BinanceSymbolPrice) AddSymbolPriceItem(symbol string, SymbolPrice SymbolPrice) {
bst.Lock.Lock()
defer bst.Lock.Unlock()
if _, ok := bst.Item[symbol]; ok {
return
}
bst.Item[symbol] = SymbolPrice
}
func (bst *BinanceSymbolPrice) DelSymbolPriceInfo(symbol string) {
bst.Lock.Lock()
defer bst.Lock.Unlock()
if _, ok := bst.Item[symbol]; !ok {
return
}
delete(bst.Item, symbol)
}
// 更新交易對價格
func (bst *BinanceSymbolPrice) ResetSymbolPriceInfo(symbol string, SymbolPrice SymbolPrice) {
bst.Lock.Lock()
defer bst.Lock.Unlock()
bst.Item[symbol] = SymbolPrice
}
分析
經過自己寫測試文件,才找出來問題的原因,問題代碼如下
func (bst *BinanceSymbolPrice) GetAllSymbolPriceInfo() map[string]SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
return bst.Item
}
這段代碼的含義是取出map中的所有數據,咋一看沒什么問題,可是忽略了 map
是引用類型,這樣實際上傳遞的是地址,還是會對源數據進行訪問,從而造成了 map
讀寫並發。
修改如下:
func (bst *BinanceSymbolPrice) GetAllSymbolPriceInfo() map[string]SymbolPrice {
bst.Lock.RLock()
defer bst.Lock.RUnlock()
item := make(map[string]SymbolPrice)
for key, value := range bst.Item {
item[key] = value
}
return item
}
新初始化聲明了一個新 map
,把查詢出來的數據重新copy到新的map中,這樣再使用時,訪問到的數據就不再是同一份數據了,從而避免了 map
讀寫並發。
在Go語言1.9版本后提供了一種並發安全的 map
,sync.Map
推薦大家使用。
這篇文章解釋了如何使用 go sync.map的使用