面试题答案
一键面试- 锁的粒度调整
- 减小锁粒度:
- 对于共享资源,尽量按照业务功能或者数据结构的逻辑进行细分。例如,假设有一个电商系统,订单数据中有订单基本信息、订单商品列表、订单物流信息等不同部分。如果原来用一把大锁保护整个订单数据结构,可以将其拆分为三把锁,分别保护订单基本信息、订单商品列表、订单物流信息。
type Order struct { BaseInfo OrderBaseInfo GoodsList []Goods Logistics LogisticsInfo } var baseInfoMutex sync.Mutex var goodsListMutex sync.Mutex var logisticsMutex sync.Mutex func UpdateOrderBaseInfo(order *Order, newBaseInfo OrderBaseInfo) { baseInfoMutex.Lock() order.BaseInfo = newBaseInfo baseInfoMutex.Unlock() } func AddGoodsToOrder(order *Order, newGoods Goods) { goodsListMutex.Lock() order.GoodsList = append(order.GoodsList, newGoods) goodsListMutex.Unlock() } func UpdateOrderLogistics(order *Order, newLogistics LogisticsInfo) { logisticsMutex.Lock() order.Logistics = newLogistics logisticsMutex.Unlock() }
- 这样,不同的业务操作可以并行执行,提高系统并发性能。
- 增大锁粒度:在某些情况下,如果多个资源总是一起被访问和修改,增大锁粒度可以减少锁竞争。例如,在一个游戏场景中,玩家的位置、生命值、经验值等属性总是在玩家移动时一起更新,那么可以用一把锁保护这些属性。
type Player struct { Position Position Health int Exp int } var playerMutex sync.Mutex func MovePlayer(player *Player, newPosition Position) { playerMutex.Lock() player.Position = newPosition // 可能会根据移动逻辑调整生命值和经验值 player.Health -= 1 player.Exp += 10 playerMutex.Unlock() }
- 减小锁粒度:
- 获取顺序设计
- 统一锁获取顺序:为所有需要获取多个锁的操作,定义一个固定的锁获取顺序。例如,有锁A、锁B、锁C,无论在哪个函数中获取这三把锁,都按照A -> B -> C的顺序获取。
var lockA sync.Mutex var lockB sync.Mutex var lockC sync.Mutex func DoComplexOperation() { lockA.Lock() defer lockA.Unlock() lockB.Lock() defer lockB.Unlock() lockC.Lock() defer lockC.Unlock() // 执行复杂业务逻辑 }
- 使用层次化锁:如果系统中有不同层次的资源,可以按照层次结构来获取锁。比如在一个文件系统模拟中,有目录和文件,获取文件锁之前先获取目录锁,按照目录 -> 文件的层次顺序获取锁。
type Directory struct { Name string Files []*File mutex sync.Mutex } type File struct { Name string Data []byte mutex sync.Mutex } func ReadFile(directory *Directory, fileName string) ([]byte, error) { directory.mutex.Lock() defer directory.mutex.Unlock() for _, file := range directory.Files { if file.Name == fileName { file.mutex.Lock() defer file.mutex.Unlock() return file.Data, nil } } return nil, fmt.Errorf("file not found") }
- 统一锁获取顺序:为所有需要获取多个锁的操作,定义一个固定的锁获取顺序。例如,有锁A、锁B、锁C,无论在哪个函数中获取这三把锁,都按照A -> B -> C的顺序获取。
- 死锁检测机制
- 使用Go语言的runtime包:Go语言的
runtime
包提供了一些工具来检测死锁。可以在程序启动时,开启一个后台协程,定期调用runtime.Gosched()
和runtime.Stack()
来收集栈信息,分析是否存在死锁。虽然这种方式不能实时检测死锁,但可以在程序运行过程中发现潜在死锁。package main import ( "fmt" "runtime" "time" ) var lock1 sync.Mutex var lock2 sync.Mutex func main() { go func() { for { time.Sleep(5 * time.Second) var stacktrace [][]byte var allGoroutines []runtime.StackRecord for i := 0; ; i++ { var record runtime.StackRecord n := runtime.StackWithAttributes(&record, true) if n == 0 { break } stacktrace = append(stacktrace, record.Stack[:n]) allGoroutines = append(allGoroutines, record) } // 这里可以添加更复杂的死锁分析逻辑,简单示例仅打印栈信息 for _, s := range stacktrace { fmt.Println(string(s)) } } }() go func() { lock1.Lock() defer lock1.Unlock() time.Sleep(1 * time.Second) lock2.Lock() defer lock2.Unlock() }() go func() { lock2.Lock() defer lock2.Unlock() time.Sleep(1 * time.Second) lock1.Lock() defer lock1.Unlock() }() select {} }
- 使用第三方库:例如
go-deadlock
库,它可以在程序运行时实时检测死锁。引入该库后,在需要检测死锁的地方调用相关函数即可。
当死锁发生时,package main import ( "fmt" "github.com/sasha-s/go-deadlock" "sync" "time" ) var lock1 sync.Mutex var lock2 sync.Mutex func main() { go func() { deadlock.Occurred() // 初始化死锁检测 lock1.Lock() defer lock1.Unlock() time.Sleep(1 * time.Second) lock2.Lock() defer lock2.Unlock() }() go func() { lock2.Lock() defer lock2.Unlock() time.Sleep(1 * time.Second) lock1.Lock() defer lock1.Unlock() }() select {} }
go-deadlock
库会输出详细的死锁信息,帮助定位问题。
- 使用Go语言的runtime包:Go语言的