今天,我在公司程式碼庫中遇到了這段程式碼(程式碼和註解是為了演示目的而重寫的,不包含任何專有程式碼):
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 用於追蹤程式碼庫中 Goroutine 的使用情況,例如 Goroutine 的數量、每個 Goroutine 所花費的時間等。 Go 方法用於啟動新的 Goroutine 並追蹤它。 Wait 方法用於等待所有 goroutine 完成。
使用範例:
g := NewGoroutineTracker() g.Go(ctx, "task1", doTask1, arg1, arg2) g.Go(ctx, "task2", doTask2, arg3) g.Wait()
嗯,該程式碼可以工作,但它使用反射包來檢查函數簽名然後調用該函數。這是完全不必要的,我們可以透過將用法更改為來避免它:
g := NewGoroutineTracker() g.Go(ctx, "task1", func() error { return doTask1(arg1, arg2) }) g.Go(ctx, "task2", func() error { return doTask2(arg3) })
新程式碼將更加簡單,並且有很多好處:
這是重構後的程式碼:
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 的另一個用例是在關閉應用程式之前等待所有 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() 等待所有 goroutine 完成後再關閉應用程式:
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 的原始實作可以工作並且可以追蹤 goroutine,但它使用 Reflect 套件來動態檢查和呼叫函數會帶來不必要的複雜性和潛在的運行時錯誤。透過重構程式碼以直接接受函數文字,我們提高了類型安全性、簡化了錯誤處理並增強了可讀性。這種方法利用 Go 的編譯器檢查類型系統來確保函數簽章和參數之間的相容性,從而產生更強壯和可維護的程式碼。透過採用這些更改,我們優化了 GoroutineTracker 的清晰度和可靠性,與 Go 程式設計的最佳實踐保持一致。
我是奧利佛‧阮。一家主要使用 Go 和 JavaScript 工作的軟體製造商。我喜歡每天學習並看到更好的自己。偶爾會衍生出新的開源專案。在我的旅程中分享知識和想法。
以上是重構:GoroutineTracker 不必要地使用反射的詳細內容。更多資訊請關注PHP中文網其他相關文章!