最近提交了一份死锁代码,导致某个功能不可用,前端小哥纳闷反馈,这昨天还能用的,今天怎么就不行了?
再一看原来是死锁了
bug
官方是否提供了死锁检测呢?
解决上网搜了一圈,发现官网没有死锁检测,接下来介绍今天的主角:https://github.com/sasha-s/go-deadlock,这是基于运行时的代码检测的。
deadlocksync
先来看一个简单使用的例子:
// 测试 deadlock 的锁检测超时时间
// 同一把锁在不同的协程中去获取
func tGoDeadlock1() {
start := time.Now()
defer func() {
fmt.Println("耗时:", time.Now().Sub(start))
}()
var wg sync.WaitGroup
wg.Add(1)
deadlock.Opts.DeadlockTimeout = time.Second // 获取锁超时控制
//deadlock.Opts.Disable = true // 生产环境可以将其设置为 true
var mu deadlock.Mutex
mu.Lock()
go func() {
mu.Lock()
defer mu.Unlock()
fmt.Println("i'm in goroutine")
wg.Done()
}()
time.Sleep(time.Second * 15)
mu.Unlock()
wg.Wait()
}
panicpanic1sdeadlock.Opts.DeadlockTimeout = time.Second1s
源码分析
locklockOrder
type lockOrder struct {
mu sync.Mutex
cur map[interface{}]stackGID // stacktraces + gids for the locks currently taken.
order map[beforeAfter]ss // expected order of locks.
}
type stackGID struct {
stack []uintptr
gid int64
}
type beforeAfter struct {
before interface{}
after interface{}
}
type ss struct {
before []uintptr
after []uintptr
}
下面再贴一小部分源码,看看其具体的过程。
获取锁:
func (m *Mutex) Lock() {
lock(m.mu.Lock, m)
}
func lock(lockFn func(), ptr interface{}) {
if Opts.Disable {
lockFn()
return
}
stack := callers(1)
preLock(stack, ptr)
if Opts.DeadlockTimeout <= 0 {
lockFn()
} else {
ch := make(chan struct{})
currentID := goid.Get()
go func() {
for {
t := time.NewTimer(Opts.DeadlockTimeout)
defer t.Stop() // This runs after the losure finishes, but it's OK.
select {
case <-t.C:
lo.mu.Lock()
prev, ok := lo.cur[ptr]
if !ok {
lo.mu.Unlock()
break // Nobody seems to be holding the lock, try again.
}
Opts.mu.Lock()
fmt.Fprintln(Opts.LogBuf, header)
fmt.Fprintln(Opts.LogBuf, "Previous place where the lock was grabbed")
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", prev.gid, ptr)
printStack(Opts.LogBuf, prev.stack)
fmt.Fprintln(Opts.LogBuf, "Have been trying to lock it again for more than", Opts.DeadlockTimeout)
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", currentID, ptr)
printStack(Opts.LogBuf, stack)
stacks := stacks()
grs := bytes.Split(stacks, []byte("\n\n"))
for _, g := range grs {
if goid.ExtractGID(g) == prev.gid {
fmt.Fprintln(Opts.LogBuf, "Here is what goroutine", prev.gid, "doing now")
Opts.LogBuf.Write(g)
fmt.Fprintln(Opts.LogBuf)
}
}
lo.other(ptr)
if Opts.PrintAllCurrentGoroutines {
fmt.Fprintln(Opts.LogBuf, "All current goroutines:")
Opts.LogBuf.Write(stacks)
}
fmt.Fprintln(Opts.LogBuf)
if buf, ok := Opts.LogBuf.(*bufio.Writer); ok {
buf.Flush()
}
Opts.mu.Unlock()
lo.mu.Unlock()
Opts.OnPotentialDeadlock()
<-ch
return
case <-ch:
return
}
}
}()
lockFn()
postLock(stack, ptr)
close(ch)
return
}
postLock(stack, ptr)
}
func preLock(stack []uintptr, p interface{}) {
lo.preLock(stack, p)
}
func (l *lockOrder) preLock(stack []uintptr, p interface{}) {
if Opts.DisableLockOrderDetection {
return
}
gid := goid.Get()
l.mu.Lock()
for b, bs := range l.cur {
if b == p {
if bs.gid == gid {
Opts.mu.Lock()
fmt.Fprintln(Opts.LogBuf, header, "Recursive locking:")
fmt.Fprintf(Opts.LogBuf, "current goroutine %d lock %p\n", gid, b)
printStack(Opts.LogBuf, stack)
fmt.Fprintln(Opts.LogBuf, "Previous place where the lock was grabbed (same goroutine)")
printStack(Opts.LogBuf, bs.stack)
l.other(p)
if buf, ok := Opts.LogBuf.(*bufio.Writer); ok {
buf.Flush()
}
Opts.mu.Unlock()
Opts.OnPotentialDeadlock()
}
continue
}
if bs.gid != gid { // We want locks taken in the same goroutine only.
continue
}
if s, ok := l.order[beforeAfter{p, b}]; ok {
Opts.mu.Lock()
fmt.Fprintln(Opts.LogBuf, header, "Inconsistent locking. saw this ordering in one goroutine:")
fmt.Fprintln(Opts.LogBuf, "happened before")
printStack(Opts.LogBuf, s.before)
fmt.Fprintln(Opts.LogBuf, "happened after")
printStack(Opts.LogBuf, s.after)
fmt.Fprintln(Opts.LogBuf, "in another goroutine: happened before")
printStack(Opts.LogBuf, bs.stack)
fmt.Fprintln(Opts.LogBuf, "happened after")
printStack(Opts.LogBuf, stack)
l.other(p)
fmt.Fprintln(Opts.LogBuf)
if buf, ok := Opts.LogBuf.(*bufio.Writer); ok {
buf.Flush()
}
Opts.mu.Unlock()
Opts.OnPotentialDeadlock()
}
l.order[beforeAfter{b, p}] = ss{bs.stack, stack}
if len(l.order) == Opts.MaxMapSize { // Reset the map to keep memory footprint bounded.
l.order = map[beforeAfter]ss{}
}
}
l.mu.Unlock()
}
释放锁:
func (m *Mutex) Unlock() {
m.mu.Unlock()
if !Opts.Disable {
postUnlock(m)
}
}
func postUnlock(p interface{}) {
lo.postUnlock(p)
}
func (l *lockOrder) postUnlock(p interface{}) {
l.mu.Lock()
delete(l.cur, p)
l.mu.Unlock()
}
总结
MutexCondOncePoolWaitGroupLock
deadlockdeadlock.Opts.Disable = true
syncTestgo-deadlock.go