调试程序发现一个报错:fatal error: concurrent map writes 是因为多个goroutine对同一个map产出了竞争,解决这个问题的方法有两个,一个是用sync.Map,另一个是加锁。sync.map是go1.9新加的特性,这里暂且先不讨论。而且当前业务场景用读写锁完全可以解决,所以决定使用读写锁。
先温习一遍概念:
1、互斥锁
其中Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁叶叫做全局锁。
func (m *Mutex) Unlock()用于解锁m,如果在使用Unlock()前未加锁,就会引起一个运行错误.已经锁定的Mutex并不与特定的goroutine相关联,这样可以利用一个goroutine对其加锁,再利用其他goroutine对其解锁。
互斥锁只能锁定一次,当在解锁之前再次进行加锁,便会无法加锁,如果在加锁前解锁,便会报错“panic: sync: unlock of unlocked mutex”
2、读写锁
RWMutex是一个读写锁,该锁可以加多个读锁或者一个写锁,其经常用于读次数远远多于写次数的场景.
func (rw *RWMutex) Lock() 写锁,如果在添加写锁之前已经有其他的读锁和写锁,则lock就会阻塞直到该锁可用,为确保该锁最终可用,已阻塞的 Lock 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定 func (rw *RWMutex) Unlock() 写锁解锁,如果没有进行写锁定,则就会引起一个运行时错误
func (rw *RWMutex) RLock() 读锁,当有写锁时,无法加载读锁,当只有读锁或者没有锁时,可以加载读锁,读锁可以加载多个,所以适用于"读多写少"的场景
func (rw *RWMutex) RUnlock() 读锁解锁,RUnlock 撤销单次RLock 调用,它对于其它同时存在的读取器则没有效果。若 rw 并没有为读取而锁定,调用 RUnlock 就会引发一个运行时错误
概括:
type MapWithLock struct { //把读写锁和资源map封装在一起
sync.RWMutex
M map[string]Kline
}
var KlineDataMemory = make(map[string] interface{})
var InstrLock sync.RWMutex
var counter = MapWithLock{ // 生成一个带有锁和map的实例,然后就可以使用啦
InstrLock,
KlineDataMemory,
}
.
.
.
counter.RLock() //加读锁
for _, v := range counter.M {
instrID = v.InstrumentID
break
}
counter.RUnlock() //解除读锁 加锁和解锁必须成对出现,并且建议放在同一层次的代码块中
在游戏开发过程中,对于新手来说也会遇到,特别是对于Go语言并发安全比较好实现的情况,很多人开发过程中就忽略了这一点。
并发安全map可以自己实现,也可以使用网上的第三方库;下面推荐给大家一个很好用的第三方的库:
github.com/fanliao/go-concurrentMap
实例例子 如下:
type ProxyServer struct {
Connection *websocket.Conn
StrMD5 string
MapSafe *concurrent.ConcurrentMap
}
数据保存 如下:
// 客户端发消息---> 服务器保存
func (this *ProxyServer) ServerSendDataToClient(ProtocolData map[string]interface{}) {
if ProtocolData["OpenID"] == nil || ProtocolData["Data"] == nil {
panic("error")
}
strOpenID := ProtocolData["OpenID"].(string)
strData := ProtocolData["Data"].(map[string]interface{})
val, err := this.MapSafe.Get(strOpenID + "|User")
if err != nil {
fmt.Println("ServerSendDataToClient:", err)
}
if val != nil {
val.(*ProxyServer).PlayerSendMessage(strData)
}
return
}
数据获取 如下:
// 客户端发消息---> 服务器保存
func (this *ProxyServer) ClientSendDataToServer(ProtocolData map[string]interface{}) {
if ProtocolData["ServerID"] == nil || ProtocolData["Data"] == nil {
panic("error")
}
strServerID := ProtocolData["ServerID"].(string)
strData := ProtocolData["Data"].(map[string]interface{})
val, _ := this.MapSafe.Get(strServerID + "|Server")
if val != nil {
val.(*ProxyServer).PlayerSendMessage(strData)
}
return
}
参考资料:
Go语言读写锁
https://blog.csdn.net/jeffrey11223/article/details/79243084