Hari ini, saya menemui kod ini dalam pangkalan kod syarikat saya (kod dan ulasan ditulis semula untuk tujuan demo dan tidak termasuk sebarang kod proprietari):
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() }
GroutineTracker digunakan untuk menjejak penggunaan goroutine dalam pangkalan kod, contohnya, bilangan goroutine, masa yang diambil oleh setiap goroutine, dll. Kaedah Go digunakan untuk memulakan goroutine baharu dan menjejakinya. Kaedah Tunggu digunakan untuk menunggu semua goroutine selesai.
Contoh penggunaan:
g := NewGoroutineTracker() g.Go(ctx, "task1", doTask1, arg1, arg2) g.Go(ctx, "task2", doTask2, arg3) g.Wait()
Nah, kod itu berfungsi, tetapi ia menggunakan pakej reflect untuk menyemak tandatangan fungsi kemudian panggil fungsi tersebut. Ia sama sekali tidak perlu, dan kita boleh mengelakkannya dengan menukar penggunaan kepada:
g := NewGoroutineTracker() g.Go(ctx, "task1", func() error { return doTask1(arg1, arg2) }) g.Go(ctx, "task2", func() error { return doTask2(arg3) })
Kod baharu akan menjadi lebih ringkas dan mempunyai banyak faedah:
Berikut ialah kod yang difaktorkan semula:
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() }() }
Satu lagi kes penggunaan untuk GoroutineTracker ialah menunggu semua goroutine selesai sebelum menutup aplikasi. Jadi kita boleh ada 2 jenis penantian:
Kami boleh melaksanakannya dengan menambahkan penjejak global dan membuat mana-mana penjejak mendaftarkan fungsinya kepada penjejak 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() }
Dan kita boleh menggunakan WaitForAll() untuk menunggu semua goroutine selesai sebelum menutup aplikasi:
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() }
Kesimpulannya, sementara pelaksanaan asal GoroutineTracker berfungsi dan boleh menjejaki goroutine, penggunaan pakej mencerminkan untuk menyemak dan memanggil fungsi secara dinamik memperkenalkan kerumitan yang tidak perlu dan kemungkinan ralat masa jalan. Dengan memfaktorkan semula kod untuk menerima literal fungsi secara langsung, kami mencapai keselamatan jenis yang lebih baik, pengendalian ralat yang diperkemas dan kebolehbacaan yang dipertingkatkan. Pendekatan ini memanfaatkan sistem jenis yang disemak pengkompil Go untuk memastikan keserasian antara tandatangan fungsi dan argumen, menghasilkan kod yang lebih mantap dan boleh diselenggara. Dengan menerima pakai perubahan ini, kami mengoptimumkan GoroutineTracker untuk kejelasan dan kebolehpercayaan, selaras dengan amalan terbaik dalam pengaturcaraan Go.
Saya Oliver Nguyen. Pembuat perisian kebanyakannya berfungsi dalam Go dan JavaScript. Saya seronok belajar dan melihat versi diri saya yang lebih baik setiap hari. Sekali-sekala putarkan projek sumber terbuka baharu. Kongsi ilmu dan fikiran sepanjang perjalanan saya.
Atas ialah kandungan terperinci Refactor: GoroutineTracker dengan penggunaan refleksi yang tidak perlu. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!