Heute bin ich auf diesen Code in der Codebasis meines Unternehmens gestoßen (Der Code und die Kommentare wurden zu Demozwecken neu geschrieben und enthalten keinen proprietären Code):
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() }
Der GoroutineTracker wird zum Verfolgen der Nutzung von Goroutinen in der Codebasis verwendet, beispielsweise die Anzahl der Goroutinen, die von jeder Goroutine benötigte Zeit usw. Die Go-Methode wird verwendet, um eine neue Goroutine zu starten und diese zu verfolgen. Mit der Wait-Methode wird darauf gewartet, dass alle Goroutinen abgeschlossen sind.
Verwendungsbeispiel:
g := NewGoroutineTracker() g.Go(ctx, "task1", doTask1, arg1, arg2) g.Go(ctx, "task2", doTask2, arg3) g.Wait()
Nun, dieser Code funktioniert, aber er verwendet das Reflect-Paket, um die Funktionssignatur zu überprüfen und dann die Funktion aufzurufen. Es ist völlig unnötig und wir können es vermeiden, indem wir die Verwendung ändern in:
g := NewGoroutineTracker() g.Go(ctx, "task1", func() error { return doTask1(arg1, arg2) }) g.Go(ctx, "task2", func() error { return doTask2(arg3) })
Der neue Code wird einfacher sein und bietet viele Vorteile:
Hier ist der überarbeitete Code:
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() }() }
Ein weiterer Anwendungsfall für GoroutineTracker besteht darin, zu warten, bis alle Goroutinen abgeschlossen sind, bevor die Anwendung heruntergefahren wird. Wir können also zwei Arten des Wartens haben:
Wir können es implementieren, indem wir einen globalen Tracker hinzufügen und jeden Tracker dazu bringen, seine Funktion beim globalen Tracker zu registrieren:
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() }
Und wir können WaitForAll() verwenden, um zu warten, bis alle Goroutinen abgeschlossen sind, bevor wir die Anwendung schließen:
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() }
Zusammenfassend lässt sich sagen, dass die ursprüngliche Implementierung von GoroutineTracker zwar funktioniert und Goroutinen verfolgen kann, die Verwendung des Reflect-Pakets zum dynamischen Überprüfen und Aufrufen von Funktionen jedoch zu unnötiger Komplexität und potenziellen Laufzeitfehlern führt. Indem wir den Code so umgestalten, dass er Funktionsliterale direkt akzeptiert, erreichen wir eine verbesserte Typsicherheit, eine optimierte Fehlerbehandlung und eine verbesserte Lesbarkeit. Dieser Ansatz nutzt das vom Compiler überprüfte Typsystem von Go, um die Kompatibilität zwischen Funktionssignaturen und Argumenten sicherzustellen, was zu einem robusteren und wartbareren Code führt. Durch die Übernahme dieser Änderungen optimieren wir den GoroutineTracker im Hinblick auf Klarheit und Zuverlässigkeit und orientieren uns dabei an den Best Practices der Go-Programmierung.
Ich bin Oliver Nguyen. Ein Softwarehersteller, der hauptsächlich mit Go und JavaScript arbeitet. Ich genieße es, zu lernen und jeden Tag eine bessere Version meiner selbst zu sehen. Gelegentlich Spin-off neuer Open-Source-Projekte. Teile Wissen und Gedanken während meiner Reise.
Das obige ist der detaillierte Inhalt vonRefactor: GoroutineTracker mit unnötiger Verwendung von Reflect. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!