go map fatal error: concurrent map iteration and map write 读写锁与深度拷贝的坑

从币安实时拉取交易对的数据,这里使用了 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版本后提供了一种并发安全的 mapsync.Map 推荐大家使用。

 

 

一.问题

map是线程不安全的,即使并发读写没有冲突也会报错(fatal error: concurrent map read and map write):

 

  1.  
    func main() {
  2.  
    m := make(map[int]int)
  3.  
    go func() {
  4.  
    for {
  5.  
    _ = m[1]
  6.  
    }
  7.  
    }()
  8.  
    go func() {
  9.  
    for {
  10.  
    m[2] = 2
  11.  
    }
  12.  
    }()
  13.  
    time.Sleep(time.Second * 1<em>)
  14.  
    }</em>

报错原因:


写的时候设置了h.flags,获取的时候检查报错

写完会清除标志位

 

二.解决办法

1)使用读写锁sync.RWMutex:

 

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

  1.  
    counter.RLock()
  2.  
    n := counter.m["some_key"]
  3.  
    counter.RUnlock()
  4.  
    fmt.Println("some_key:", n)
  1.  
    counter.Lock()
  2.  
    counter.m["some_key"]++
  3.  
    counter.Unlock()



2)使用类似java的Concurrenthashmap(https://github.com/orcaman/concurrent-map)

3)使用sync.Map

 

  1.  
    func main() {
  2.  
    m := sync.Map{}
  3.  
    m.Store(1, 1)
  4.  
    m.Store("1", "1")
  5.  
    fmt.Println(m.Load("1"))
  6.  
    m.Delete("1")
  7.  
    fmt.Println(m.Load("1"))
  8.  
    }


补充:对于不容易发现的并发问题,可以使用-race参数进行并发检测

 

  1.  
    func main() {
  2.  
    a := 1
  3.  
    go func() {
  4.  
    a = 2
  5.  
    }()
  6.  
    a = 3
  7.  
    fmt.Println(a)
  8.  
     
  9.  
    time.Sleep(time.Second * 1)
  10.  
    }



posted on 2022-08-19 15:00  ExplorerMan  阅读(528)  评论(0编辑  收藏  举报

导航