我在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 可能会有利可图。您可以首先将其锁定为只读,如果高度管理器已经存在,则可以将其返回。这样做的好处是,多个读者可以同时访问它而不会互相阻塞。仅当发现高度管理器不存在时,才需要获取写锁定。

07-25 23:35