Refactor: GoroutineTracker mit unnötiger Verwendung von Reflect

PHPz
Freigeben: 2024-07-17 04:26:29
Original
633 Leute haben es durchsucht

Refactor: GoroutineTracker with unnecessary usage of reflect

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() }
Nach dem Login kopieren

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()
Nach dem Login kopieren

Problem: Die Verwendung von Reflect ist unnötig und kann vermieden werden

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)
})
Nach dem Login kopieren

Der neue Code wird einfacher sein und bietet viele Vorteile:

  • Typsicherheit: Die Funktionssignatur muss nicht mit Reflect überprüft werden. Der Compiler erledigt das für uns. Der Originalcode weist potenzielle Laufzeitfehler auf, wenn die Funktionssignatur nicht mit den Argumenten übereinstimmt.
  • Fehlerbehandlung: Wir können einen Fehler von der Funktion zurückgeben und ihn im Aufrufer behandeln. Der Originalcode ignoriert die Ausgabe der Funktion.
  • Lesbarkeit: Der neue Code ist besser lesbar und leichter zu verstehen. Wir können die Funktionssignatur und die Argumente direkt im Code sehen.

Eine bessere Implementierung von GoroutineTracker

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()
    }()
}
Nach dem Login kopieren

Warten Sie, bis alle Goroutinen abgeschlossen sind, bevor Sie sie herunterfahren

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:

  • In einer Funktion: Warten auf den Abschluss aller lokalen Goroutinen.
  • Beim Herunterfahren der Anwendung: Warten auf den Abschluss aller Goroutinen, die von einem GoroutineTracker gestartet wurden.

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() }
Nach dem Login kopieren

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()
}
Nach dem Login kopieren

Abschluss

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.


Autor

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!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!