MST
星途 面试题库

面试题:Go状态机实现中的并发安全问题及解决方案

在Go语言实现状态机时,如果存在多个协程同时访问和修改状态机的状态,可能会出现并发安全问题。请分析可能出现的并发问题场景,并给出至少两种有效的解决方案,同时用代码示例说明。
14.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

可能出现的并发问题场景

  1. 竞态条件(Race Condition):多个协程同时读取和修改状态机的状态,导致最终状态取决于协程执行的顺序,结果不可预测。例如,状态机中有一个计数器count,两个协程同时对其进行count++操作,由于这不是原子操作,可能会丢失一个增量。

解决方案

  1. 互斥锁(Mutex)
    • 使用sync.Mutex来保护对状态机状态的访问。在读取或修改状态前,获取锁;操作完成后,释放锁。这样同一时间只有一个协程能访问和修改状态,避免竞态条件。
    • 代码示例:
package main

import (
    "fmt"
    "sync"
)

type StateMachine struct {
    state int
    mu    sync.Mutex
}

func (sm *StateMachine) Transition() {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    sm.state++
}

func main() {
    var wg sync.WaitGroup
    sm := StateMachine{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            sm.Transition()
        }()
    }
    wg.Wait()
    fmt.Println("Final state:", sm.state)
}
  1. 读写锁(RWMutex)
    • 如果状态机的读取操作远多于写入操作,可以使用sync.RWMutex。读操作时允许多个协程同时进行,写操作时则独占,防止读写冲突和写写冲突。
    • 代码示例:
package main

import (
    "fmt"
    "sync"
)

type StateMachine struct {
    state int
    rwmu  sync.RWMutex
}

func (sm *StateMachine) ReadState() int {
    sm.rwmu.RLock()
    defer sm.rwmu.RUnlock()
    return sm.state
}

func (sm *StateMachine) WriteState() {
    sm.rwmu.Lock()
    defer sm.rwmu.Unlock()
    sm.state++
}

func main() {
    var wg sync.WaitGroup
    sm := StateMachine{}
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            sm.WriteState()
        }()
    }
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            state := sm.ReadState()
            fmt.Println("Read state:", state)
        }()
    }
    wg.Wait()
}
  1. 通道(Channel)
    • 通过通道来传递对状态机的操作请求,状态机本身在一个单独的协程中处理这些请求,这样就避免了多个协程直接并发访问状态。
    • 代码示例:
package main

import (
    "fmt"
    "sync"
)

type StateMachine struct {
    state int
    ch    chan func()
}

func NewStateMachine() *StateMachine {
    sm := &StateMachine{
        ch: make(chan func()),
    }
    go func() {
        for f := range sm.ch {
            f()
        }
    }()
    return sm
}

func (sm *StateMachine) Transition() {
    sm.ch <- func() {
        sm.state++
    }
}

func main() {
    var wg sync.WaitGroup
    sm := NewStateMachine()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            sm.Transition()
        }()
    }
    wg.Wait()
    sm.ch <- func() {
        fmt.Println("Final state:", sm.state)
    }
    close(sm.ch)
}