我在Go中编写一些程序,但出现此崩溃:
fatal error: concurrent map read and map write
consensus/bft.(*ConsensusManager).getHeightManager(0xc42009a7e0, 0x37, 0x0)
consensus/bft/bft_manager.go:246 +0x9b fp=0xc42b033258 sp=0xc42b033208 pc=0xaf1d7b
consensus/bft.(*HeightManager).Round(...)
consensus/bft/bft_manager.go:239
consensus/bft.(*ConsensusManager).Round(0xc42009a7e0, 0x37)
这是我的代码
type ConsensusManager struct {
pm *ProtocolManager
chain *core.BlockChain
coinbase common.Address
readyValidators map[common.Address]struct{}
trackedProtocolFailures []string
heights map[uint64]*HeightManager
blockCandidates map[common.Hash]btypes.Proposal
currentBlock *types.Block
found chan *types.Block
mu sync.Mutex
writeMapMu sync.RWMutex
getHeightMu sync.RWMutex
processMu sync.Mutex
}
func (cm *ConsensusManager) Round() uint64 {
return cm.getHeightManager(cm.Height()).Round()
}
func (cm *ConsensusManager) getHeightManager(h uint64) *HeightManager {
if _, ok := cm.heights[h]; !ok {
cm.heights[h] = NewHeightManager(cm, h)
}
return cm.heights[h]
}
我尝试使用RWMutex来运气,但是代码无法正常工作
func (cm *ConsensusManager) Round() uint64 {
cm.getHeightMu.Lock()
defer cm.getHeightMu.Unlock()
return cm.getHeightManager(cm.Height()).Round()
}
func (cm *ConsensusManager) getHeightManager(h uint64) *HeightManager {
cm.getHeightMu.Lock()
defer cm.getHeightMu.Unlock()
if _, ok := cm.heights[h]; !ok {
cm.heights[h] = NewHeightManager(cm, h)
}
return cm.heights[h]
}
我的解决方案出了什么问题?
最佳答案
在Round()
中,您可以锁定getHeightMu
互斥锁,然后调用getHeightManager()
。您再次尝试在其中锁定相同的互斥锁。
Go的互斥锁不是reentrant,这意味着如果它已经被锁定,则无法再被同一goroutine再次锁定。有关详细信息,请参见Recursive locking in Go。
尝试锁定已经锁定的互斥锁是一项阻止操作。只要互斥锁被解锁,并且您的goroutine是幸运的,它将再次锁定它(如果其他goroutine也正在等待它),它将阻塞。但是,只有在Round()
返回时需要解锁getHeightManager()
才能解锁,这永远不会发生。这是一个僵局。
您只需要将互斥锁锁定在一个位置即可访问它。因此,仅在getHeightManager()
内部使用锁定,然后从Round()
中删除锁定(无论如何它都不会访问 protected 资源)。
此外,取决于创建新的高度管理器并将其添加到 map 的频率,使用 sync.RWMutex
可能会有利可图。您可以首先将其锁定为只读,如果高度管理器已经存在,则可以将其返回。这样做的好处是,多个读者可以同时访问它而不会互相阻塞。仅当发现高度管理器不存在时,才需要获取写锁定。