面试题答案
一键面试避免死锁的策略及代码示例
- 按顺序加锁
- 策略阐述:在多个互斥锁存在的情况下,约定所有的goroutine都按照相同的顺序获取锁。这样可以避免循环依赖导致的死锁。
- 代码示例:
package main
import (
"fmt"
"sync"
)
var (
mu1 sync.Mutex
mu2 sync.Mutex
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
// goroutine 1
go func() {
defer wg.Done()
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine 1: acquired mu1")
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine 1: acquired mu2")
}()
// goroutine 2
go func() {
defer wg.Done()
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine 2: acquired mu1")
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine 2: acquired mu2")
}()
wg.Wait()
}
- 使用
context
控制超时- 策略阐述:通过设置获取锁的超时时间,如果在规定时间内没有获取到锁,则放弃获取,避免无限等待。
- 代码示例:
package main
import (
"context"
"fmt"
"sync"
"time"
)
var mu sync.Mutex
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ctx.Done():
fmt.Println("timed out waiting for lock")
default:
mu.Lock()
defer mu.Unlock()
fmt.Println("acquired lock")
}
}()
wg.Wait()
}
- 避免嵌套锁
- 策略阐述:尽量减少锁的嵌套使用,如果必须嵌套,要仔细规划锁的获取和释放顺序。
- 代码示例:假设我们有一个简单的场景,原本可能会出现嵌套锁的错误使用。
package main
import (
"fmt"
"sync"
)
var (
muOuter sync.Mutex
muInner sync.Mutex
)
// 错误的嵌套锁使用方式(可能导致死锁)
// func wrongNestedLock() {
// muOuter.Lock()
// defer muOuter.Unlock()
// muInner.Lock()
// defer muInner.Unlock()
// fmt.Println("Inside wrong nested lock")
// }
// 正确的方式,避免嵌套锁
func correctWay() {
// 先获取外层锁
muOuter.Lock()
// 处理外层锁相关逻辑
outerData := "outer data"
muOuter.Unlock()
// 再获取内层锁
muInner.Lock()
// 处理内层锁相关逻辑,使用外层数据
fmt.Println("Using outer data:", outerData)
muInner.Unlock()
}
- 检测死锁
- 策略阐述:使用Go语言内置的
runtime
包中的死锁检测机制,在程序运行时检测是否发生死锁,并输出相关信息。 - 代码示例:
- 策略阐述:使用Go语言内置的
package main
import (
"fmt"
"runtime"
"sync"
)
var (
mu1 sync.Mutex
mu2 sync.Mutex
)
func main() {
runtime.SetDeadlockProfileRate(1)
var wg sync.WaitGroup
wg.Add(2)
// goroutine 1
go func() {
defer wg.Done()
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine 1: acquired mu1")
time.Sleep(1 * time.Second)
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine 1: acquired mu2")
}()
// goroutine 2
go func() {
defer wg.Done()
mu2.Lock()
defer mu2.Unlock()
fmt.Println("goroutine 2: acquired mu2")
time.Sleep(1 * time.Second)
mu1.Lock()
defer mu1.Unlock()
fmt.Println("goroutine 2: acquired mu1")
}()
wg.Wait()
}
当运行这段代码时,如果发生死锁,Go运行时会输出死锁相关的堆栈信息,帮助开发者定位问题。