go でのデッドロック検出、サードパーティ ツール go-deadlock

序文

最近、デッドロック コードが送信され、特定の機能が使用できなくなりました。フロントエンド担当者は、フィードバックについて疑問に思っていました。昨日は機能しましたが、今日はなぜ機能しないのでしょうか?

改めて見てみると行き詰まりでした。
気まずい

質問

  1. コードはセルフテストされておらず、非常に単純な修正で問題は発生しないと思いましたbug
  2. git commit 後の CI はデッドロックをチェックしません。

公式はデッドロック検出を提供していますか?

解決する

インターネットで検索したところ、公式 Web サイトにはデッドロック検出がないことがわかりました。今日の主役であるランタイム コード検出をベースにしたhttps://github.com/sasha-s/go-deadlockを紹介しましょう。

deadlock標準ライブラリの同期プリミティブではなく、sync標準ライブラリの同期プリミティブを使用することでコードに反映されます。

まずは簡単な使用例を見てみましょう。

// 测试 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()
}

2 つのコルーチンが同じロックを同時に取得します。この時点でデッドロックが発生し、プロセスが終了しないことは誰もが知っています。ただし、上記のコードの出力は次のとおりです。
ここに画像の説明を挿入します
単純明快で、ロックの試行にかかる時間が超過しているとpanic言われています。_ ここで追加したのは、取得ロック超過検出を として設定することですpanic1sdeadlock.Opts.DeadlockTimeout = time.Second1s

ソースコード分析

ソース コードを開くと、その原理はそれほど複雑ではなく、簡単に言うと、ロック時にこのメソッドがトリガーされ、lock競合するロック関係があるかどうかを検出します。lockOrderロックされたメモリ情報はによって保存され、その構造は次のとおりです。

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()
}

要約する

go-deadlock は、公式標準ライブラリの 、 、 、 などをカプセル化し、MutexメソッドCondOnceデッドPoolロック判定ロジックの層を追加したもので、ソースコードが少ないので独学でも学習可能です。WaitGroupLock

注:運用環境では直接使用しないことをお勧めします。忘れずに設定してくださいdeadlockdeadlock.Opts.Disable = true

関連するコードは、私のGithub/GoTest ウェアハウスsyncTestのディレクトリにあるファイルにありますgo-deadlock.goこのプロジェクトは、主に日々の学習の記録とテストに関するものです。興味があれば、ご覧ください。

おすすめ

転載: blog.csdn.net/DisMisPres/article/details/123402901