Aujourd'hui, j'ai rencontré ce code dans la base de code de mon entreprise (le code et les commentaires sont réécrits à des fins de démonstration et n'incluent aucun code propriétaire) :
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() }
Le GoroutineTracker est utilisé pour suivre l'utilisation des goroutines dans la base de code, par exemple, le nombre de goroutines, le temps pris par chaque goroutine, etc. La méthode Go est utilisée pour démarrer une nouvelle goroutine et la suivre. La méthode Wait est utilisée pour attendre la fin de toutes les goroutines.
Exemple d'utilisation :
g := NewGoroutineTracker() g.Go(ctx, "task1", doTask1, arg1, arg2) g.Go(ctx, "task2", doTask2, arg3) g.Wait()
Eh bien, ce code fonctionne, mais il utilise le package Reflect pour vérifier la signature de la fonction puis appeler la fonction. C'est totalement inutile, et nous pouvons l'éviter en changeant l'usage en :
g := NewGoroutineTracker() g.Go(ctx, "task1", func() error { return doTask1(arg1, arg2) }) g.Go(ctx, "task2", func() error { return doTask2(arg3) })
Le nouveau code sera plus simple et présente de nombreux avantages :
Voici le code refactorisé :
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() }() }
Un autre cas d'utilisation de GoroutineTracker consiste à attendre la fin de toutes les goroutines avant d'arrêter l'application. On peut donc avoir 2 types d'attente :
Nous pouvons l'implémenter en ajoutant un tracker global et en faisant en sorte que n'importe quel tracker enregistre sa fonction auprès du tracker global :
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() }
Et nous pouvons utiliser WaitForAll() pour attendre que toutes les goroutines soient terminées avant de fermer l'application :
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() }
En conclusion, bien que l'implémentation originale de GoroutineTracker fonctionne et puisse suivre les goroutines, son utilisation du package Reflect pour vérifier et appeler dynamiquement des fonctions introduit une complexité inutile et des erreurs d'exécution potentielles. En refactorisant le code pour accepter directement les littéraux de fonction, nous obtenons une sécurité de type améliorée, une gestion rationalisée des erreurs et une lisibilité améliorée. Cette approche exploite le système de types vérifiés par le compilateur Go pour garantir la compatibilité entre les signatures de fonction et les arguments, ce qui aboutit à un code plus robuste et plus maintenable. En adoptant ces changements, nous optimisons GoroutineTracker pour plus de clarté et de fiabilité, en nous alignant sur les meilleures pratiques de programmation Go.
Je m'appelle Oliver Nguyen. Un éditeur de logiciels travaillant principalement en Go et JavaScript. J'aime apprendre et voir une meilleure version de moi-même chaque jour. Lancez occasionnellement de nouveaux projets open source. Partagez vos connaissances et vos réflexions tout au long de mon voyage.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!