如何解决致命错误:并发地图读取和地图写入

时间:2019-06-18 08:24:26

标签: dictionary go concurrency synchronization

我正在用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]
}

我的解决方案出了什么问题?

1 个答案:

答案 0 :(得分:3)

Round()中,您将锁定getHeightMu互斥锁,然后调用getHeightManager()。您再次尝试在其中锁定相同的互斥锁。

Go的互斥锁不是reentrant,这意味着如果它已经被锁定,则无法再被同一goroutine再次锁定。有关详细信息,请参见Recursive locking in Go

尝试锁定已经锁定的互斥锁是一项阻止操作。只要互斥锁被解锁,并且您的goroutine是可以再次锁定它的幸运者(如果其他goroutine也正在等待它),它将锁定。但是,只有在Round()返回而要求getHeightManager()完成的情况下才能解锁,这永远不会发生。这是一个僵局。

您只需要将互斥锁锁定在一个位置即可访问它。因此,仅在getHeightManager()内部使用锁定,然后从Round()中删除锁定(无论如何它都不会访问受保护的资源)。

还要视创建新的高度管理器并将其添加到地图的频率而定,使用sync.RWMutex可能会有利可图。您可以首先将其锁定为只读,如果高度管理器已经存在,则可以将其返回。这样做的好处是,多个读者可以同时访问它而不会互相阻塞。仅当您发现高度管理器不存在时,才需要获取写锁。