リファクタリング: 不要なリフレクトを使用したGoroutineTracker
今日、会社のコードベースでこのコードに遭遇しました(コードとコメントはデモ目的で書き直されており、独自のコードは含まれていません):
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) })
新しいコードはよりシンプルになり、多くの利点があります:
- タイプセーフティ: リフレクトを使用して関数のシグネチャをチェックする必要はありません。コンパイラがそれをやってくれます。関数のシグネチャが引数と一致しない場合、元のコードにはランタイム エラーが発生する可能性があります。
- エラー処理: 関数からエラーを返し、呼び出し元で処理できます。元のコードは関数の出力を無視します。
- 可読性: 新しいコードはより読みやすく、理解しやすくなっています。コード内で関数のシグネチャと引数を直接確認できます。
GoroutineTracker のより良い実装
リファクタリングされたコードは次のとおりです:
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 種類の待機が可能です:
- 関数内: すべてのローカル goroutine が終了するのを待機しています。
- アプリケーションのシャットダウン時: いずれかの GoroutineTracker によって開始されたすべての goroutine が終了するのを待機しています。
グローバル トラッカーを追加し、任意のトラッカーにその機能をグローバル トラッカーに登録させることで、これを実装できます。
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 サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











OpenSSLは、安全な通信で広く使用されているオープンソースライブラリとして、暗号化アルゴリズム、キー、証明書管理機能を提供します。ただし、その歴史的バージョンにはいくつかの既知のセキュリティの脆弱性があり、その一部は非常に有害です。この記事では、Debian SystemsのOpenSSLの共通の脆弱性と対応測定に焦点を当てます。 Debianopensslの既知の脆弱性:OpenSSLは、次のようないくつかの深刻な脆弱性を経験しています。攻撃者は、この脆弱性を、暗号化キーなどを含む、サーバー上の不正な読み取りの敏感な情報に使用できます。

バックエンド学習パス:フロントエンドからバックエンドへの探査の旅は、フロントエンド開発から変わるバックエンド初心者として、すでにNodeJSの基盤を持っています...

Beegoormフレームワークでは、モデルに関連付けられているデータベースを指定する方法は?多くのBEEGOプロジェクトでは、複数のデータベースを同時に操作する必要があります。 Beegoを使用する場合...

Go Crawler Collyのキュースレッドの問題は、Go言語でColly Crawler Libraryを使用する問題を調査します。 �...

Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか?ゴーランドを使用するためにGolandを使用する場合、多くの開発者はカスタム構造タグに遭遇します...

redisstreamを使用してGo言語でメッセージキューを実装する問題は、GO言語とRedisを使用することです...

この記事では、自動拡張を実現するためにDebianシステムでMongodbを構成する方法を紹介します。主な手順には、Mongodbレプリカセットとディスクスペース監視のセットアップが含まれます。 1。MongoDBのインストール最初に、MongoDBがDebianシステムにインストールされていることを確認してください。次のコマンドを使用してインストールします。sudoaptupdatesudoaptinstinstall-yymongodb-org2。mongodbレプリカセットMongodbレプリカセットの構成により、自動容量拡張を達成するための基礎となる高可用性とデータ冗長性が保証されます。 Mongodbサービスを開始:Sudosystemctlstartmongodsudosys
