Как решить фатальную ошибку: одновременное чтение карты и запись карты - PullRequest
1 голос
/ 18 июня 2019

Я пишу какую-то программу на 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 Ответ

3 голосов
/ 18 июня 2019

В Round() вы блокируете мьютекс getHeightMu и звоните getHeightManager(). В котором вы снова пытаетесь заблокировать тот же мьютекс.

Мьютекс Go не является reentrant , что означает, что он не может быть снова заблокирован той же самой программой, если он уже заблокирован. Подробнее см. Рекурсивная блокировка в Go .

Попытка заблокировать уже заблокированный мьютекс является блокирующей операцией. Он будет блокироваться до тех пор, пока мьютекс будет разблокирован, а ваша подпрограмма станет счастливчиком, который снова сможет ее заблокировать (если другие подпрограммы также ожидают ее). Но разблокировка наступит только тогда, когда вернется Round(), для завершения которого требуется getHeightManager(), что никогда не произойдет. Это тупик.

Вам нужно заблокировать мьютекс только в одном месте, рядом с которым вы получаете к нему доступ. Поэтому используйте блокировку только внутри getHeightManager() и удалите блокировку из Round() (она все равно не получает доступ к защищенному ресурсу).

Также в зависимости от того, как часто новые менеджеры высоты создаются и добавляются на карту, может быть выгодно использовать sync.RWMutex. Сначала вы можете заблокировать его только для чтения, а если менеджер высоты уже существует, вы можете вернуть его. Хорошей стороной этого является то, что несколько читателей могут получить к нему доступ одновременно, не блокируя друг друга. Только если вы обнаружите, что менеджер высоты еще не существует, вам придется установить блокировку записи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...