今日、会社のコードベースでこのコードに遭遇しました(コードとコメントはデモ目的で書き直されており、独自のコードは含まれていません):
type GoroutineTracker struct { wg sync.WaitGroup // ... some other fields } // Go starts a new goroutine and tracks it with some metrics. func (g *GoroutineTracker) Go(ctx context.Context, name string, f any, args ...any) { fn := reflect.TypeOf(f) if fn.Kind() != reflect.Func { panic("must be function") } if fn.NumIn() != len(args) { panic("args does not match fn signature") } if fn.NumOut() > 0 { panic("output from fn is ignored") } g.wg.Add(1) id := g.startCaptureTime() go func() { defer func() { r := recover() // ... some panic handling code g.wg.Done() g.endCaptureTime(id) }() input := typez.MapFunc(args, func(arg any) reflect.Value { return reflect.ValueOf(arg) }) _ = reflect.ValueOf(f).Call(input) }() } // Wait for all goroutines to finished. func (g *GoroutineTracker) Wait() { g.wg.Wait() }
GoroutineTracker は、コードベース内のゴルーチンの使用状況 (ゴルーチンの数、各ゴルーチンにかかった時間など) を追跡するために使用されます。 Go メソッドは、新しいゴルーチンを開始して追跡するために使用されます。 Wait メソッドは、すべてのゴルーチンが終了するのを待つために使用されます。
使用例:
g := NewGoroutineTracker() g.Go(ctx, "task1", doTask1, arg1, arg2) g.Go(ctx, "task2", doTask2, arg3) g.Wait()
そのコードは機能しますが、reflect パッケージを使用して関数の署名を確認してから関数を呼び出します。これはまったく不要であり、使用法を次のように変更することで回避できます。
g := NewGoroutineTracker() g.Go(ctx, "task1", func() error { return doTask1(arg1, arg2) }) g.Go(ctx, "task2", func() error { return doTask2(arg3) })
新しいコードはよりシンプルになり、多くの利点があります:
リファクタリングされたコードは次のとおりです:
func (g *GoroutineTracker) Go(ctx context.Context, fn func() error) { g.wg.Add(1) id := g.startCaptureTime() go func() (err error) { defer func() { r := recover() // capture returned error and panic g.endCaptureTime(id, r, err) g.wg.Done() }() // just call the function, no reflect needed return fn() }() }
GoroutineTracker のもう 1 つの使用例は、アプリケーションをシャットダウンする前にすべての goroutine が終了するのを待つことです。したがって、2 種類の待機が可能です:
グローバル トラッカーを追加し、任意のトラッカーにその機能をグローバル トラッカーに登録させることで、これを実装できます。
type GlobalTracker struct { wg sync.WaitGroup // ... some other fields } type GoroutineTracker struct { parent *GlobalTracker wg sync.WaitGroup // ... some other fields } func (g *GlobalTracker) New() *GoroutineTracker { return &GoroutineTracker{parent: g} } func (g *GoroutineTracker) Go(ctx context.Context, fn func() error) { g.wg.Add(1) // use both parent and local wg g.parent.wg.Add(1) // to track the new goroutine id := g.startCaptureTime() go func() (err error) { defer func() { // ... g.endCaptureTime(id, r, err) g.wg.Done() g.parent.wg.Done() }() return fn() }() } func (g *GlobalTracker) WaitForAll() { g.wg.Wait() } func (g *GoroutineTracker) Wait() { g.wg.Wait() }
そして、WaitForAll() を使用して、アプリケーションをシャットダウンする前にすべてのゴルーチンが完了するのを待つことができます。
type FooService { tracker *GlobalTracker // ... some other fields } func (s *FooService) DoSomething(ctx context.Context) { g := s.tracker.New() g.Go(ctx, func() error { return s.doTask1(arg1, arg2) }) g.Go(ctx, func() error { return s.doTask2(arg3) }) g.Wait() // wait for local goroutines, this is optional } func main() { // some initialization, then start the application globalTracker := &GlobalTracker{} fooService := FooService{tracker: globalTracker, /*...*/} application.Start() // wait for all goroutines to finish before shutting down <-application.Done() globalTracker.Wait() }
結論として、GoroutineTracker の元の実装は動作し、ゴルーチンを追跡できますが、関数を動的にチェックして呼び出すためにリフレクト パッケージを使用すると、不必要な複雑さと潜在的なランタイム エラーが発生します。関数リテラルを直接受け入れるようにコードをリファクタリングすることで、型の安全性が向上し、エラー処理が合理化され、可読性が向上します。このアプローチでは、Go のコンパイラでチェックされた型システムを活用して、関数のシグネチャと引数の間の互換性を確保し、より堅牢で保守しやすいコードを実現します。これらの変更を採用することで、Go プログラミングのベスト プラクティスに合わせて、GoroutineTracker を最適化して明確さと信頼性を実現します。
私はオリバー・グエンです。主に Go と JavaScript を扱うソフトウェア メーカー。私は毎日学び、より良い自分を見ることを楽しんでいます。時々、新しいオープンソース プロジェクトをスピンオフします。私の旅中の知識や考えを共有してください。
以上がリファクタリング: 不要なリフレクトを使用したGoroutineTrackerの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。