Golang中Mutex使用问题排查指南
Golang中Mutex使用问题排查指南 我正在编写一个API,其中使用了几个goroutine来处理任务。其中两个goroutine负责通过WebSocket连接向客户端发送消息。但由于一个我无法查明的奇怪bug,每条消息都被发送了两次给客户端。因此,我决定在这里发帖寻求帮助,以理解可能出了什么问题。以下是相关代码的片段。你可以在github上查看完整代码。
helpers 包:
// BroadcastBoardUpdates 向用户广播棋盘更新
func BroadcastBoardUpdates(i *models.Instance) {
for {
if i.GetBroadcastBoardFlag() {
for x := range i.AllPlayers {
msg := models.NewStateMsg{constants.StateUpBcastMsg, i.AllPlayers[i.CurrentTurn].UserName, i.Board}
err := i.AllPlayers[x].WriteToWebsocket(msg)
if err != nil {
log.Printf("error: %v", err)
i.AllPlayers[x].CleanupWs()
}
}
i.SetBroadcastBoardFlag(false)
}
if i.IsOver {
return
}
}
}
// BroadcastWinner 向用户广播获胜者
func BroadcastWinner(i *models.Instance) {
for {
if i.IsOver {
return
}
if i.GetIfSomeoneWon() {
for x := range i.AllPlayers {
msg := models.WinnerMsg{constants.UserWonMsg, i.Winner.UserName, i.Winner.Color}
err := i.AllPlayers[x].WriteToWebsocket(msg)
if err != nil {
log.Printf("error: %v", err)
i.AllPlayers[x].CleanupWs()
}
}
i.IsOver = true
}
}
}
instance 包
// SetBroadcastBoardFlag 安全地设置广播棋盘状态标志
func (i *Instance) SetBroadcastBoardFlag(val bool) {
i.bbcastMutex.Lock()
i.broadcastBoardFlag = val
i.bbcastMutex.Unlock()
}
// GetBroadcastBoardFlag 安全地获取广播棋盘状态标志
func (i *Instance) GetBroadcastBoardFlag() bool {
i.bbcastMutex.Lock()
defer i.bbcastMutex.Unlock()
return i.broadcastBoardFlag
}
// GetIfSomeoneWon 返回是否有人获胜
func (i *Instance) GetIfSomeoneWon() bool {
i.allPlayedMutex.Lock()
val := i.didWin
i.allPlayedMutex.Unlock()
return val
}
// SetIfSomeoneWon 设置 didWin
func (i *Instance) SetIfSomeoneWon(val bool) {
i.allPlayedMutex.Lock()
i.didWin = val
i.allPlayedMutex.Unlock()
}
simulate 包
// ChainReaction 在每次移动后调用,并在棋盘上传播能量球
func ChainReaction(gameInstance *models.Instance, move models.MoveMsg) error {
helpers.SetIfAllPlayedOnce(gameInstance, player.UserName)
gameInstance.SetBroadcastBoardFlag(true)
if won {
gameInstance.SetWinner(player)
}
return nil
}
更多关于Golang中Mutex使用问题排查指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
你运行程序时使用了 -race 参数吗?这可能有助于调试问题。你能提供一个最小的可运行代码示例吗?
更多关于Golang中Mutex使用问题排查指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
问题可能出在BroadcastBoardUpdates和BroadcastWinner这两个goroutine的竞态条件上。当i.IsOver被设置为true时,两个goroutine可能同时进入发送消息的循环,导致消息重复发送。
以下是修复后的代码示例:
// BroadcastBoardUpdates 向用户广播棋盘更新
func BroadcastBoardUpdates(i *models.Instance) {
for {
i.bbcastMutex.Lock()
shouldBroadcast := i.broadcastBoardFlag
i.bbcastMutex.Unlock()
if shouldBroadcast {
i.bbcastMutex.Lock()
i.broadcastBoardFlag = false
i.bbcastMutex.Unlock()
for x := range i.AllPlayers {
msg := models.NewStateMsg{constants.StateUpBcastMsg, i.AllPlayers[i.CurrentTurn].UserName, i.Board}
err := i.AllPlayers[x].WriteToWebsocket(msg)
if err != nil {
log.Printf("error: %v", err)
i.AllPlayers[x].CleanupWs()
}
}
}
i.allPlayedMutex.Lock()
isOver := i.IsOver
i.allPlayedMutex.Unlock()
if isOver {
return
}
}
}
// BroadcastWinner 向用户广播获胜者
func BroadcastWinner(i *models.Instance) {
for {
i.allPlayedMutex.Lock()
isOver := i.IsOver
i.allPlayedMutex.Unlock()
if isOver {
return
}
i.allPlayedMutex.Lock()
shouldBroadcast := i.didWin
i.allPlayedMutex.Unlock()
if shouldBroadcast {
i.allPlayedMutex.Lock()
i.IsOver = true
i.allPlayedMutex.Unlock()
for x := range i.AllPlayers {
msg := models.WinnerMsg{constants.UserWonMsg, i.Winner.UserName, i.Winner.Color}
err := i.AllPlayers[x].WriteToWebsocket(msg)
if err != nil {
log.Printf("error: %v", err)
i.AllPlayers[x].CleanupWs()
}
}
}
}
}
在instance包中,需要添加对IsOver字段的互斥锁保护:
type Instance struct {
// ... 其他字段
IsOver bool
isOverMutex sync.Mutex
// ... 其他字段
}
// SetIsOver 安全地设置游戏结束标志
func (i *Instance) SetIsOver(val bool) {
i.isOverMutex.Lock()
i.IsOver = val
i.isOverMutex.Unlock()
}
// GetIsOver 安全地获取游戏结束标志
func (i *Instance) GetIsOver() bool {
i.isOverMutex.Lock()
defer i.isOverMutex.Unlock()
return i.IsOver
}
在simulate包中,修改ChainReaction函数:
func ChainReaction(gameInstance *models.Instance, move models.MoveMsg) error {
// ... 其他逻辑
if won {
gameInstance.SetWinner(player)
gameInstance.SetIfSomeoneWon(true)
gameInstance.SetIsOver(true)
}
return nil
}
主要问题是:
IsOver字段没有互斥锁保护,存在数据竞争- 两个广播goroutine在检查条件和执行操作之间存在时间窗口,可能导致重复执行
- 标志位的设置和检查需要原子性操作
修复方案通过互斥锁确保对共享状态的原子访问,并重新组织逻辑以避免竞态条件。

