Refactor: GoroutineTracker dengan penggunaan refleksi yang tidak perlu

PHPz
Lepaskan: 2024-07-17 04:26:29
asal
632 orang telah melayarinya

Refactor: GoroutineTracker with unnecessary usage of reflect

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() }
Salin selepas log masuk

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()
Salin selepas log masuk

Masalah: Penggunaan refleksi adalah tidak perlu dan boleh dielakkan

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)
})
Salin selepas log masuk

Kod baharu akan menjadi lebih ringkas dan mempunyai banyak faedah:

  • Keselamatan jenis: Tidak perlu menyemak tandatangan fungsi menggunakan reflect. Pengkompil akan melakukannya untuk kita. Kod asal mempunyai potensi ralat masa jalan jika tandatangan fungsi tidak sepadan dengan argumen.
  • Ralat pengendalian: Kami boleh mengembalikan ralat daripada fungsi dan mengendalikannya dalam pemanggil. Kod asal mengabaikan output fungsi.
  • Kebolehbacaan: Kod baharu lebih mudah dibaca dan lebih mudah difahami. Kita boleh melihat tandatangan fungsi dan hujah terus dalam kod.

Pelaksanaan GoroutineTracker yang lebih baik

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()
    }()
}
Salin selepas log masuk

Tunggu sehingga semua goroutine selesai sebelum ditutup

Satu lagi kes penggunaan untuk GoroutineTracker ialah menunggu semua goroutine selesai sebelum menutup aplikasi. Jadi kita boleh ada 2 jenis penantian:

  • Dalam satu majlis: Menunggu semua gorouti tempatan selesai.
  • Apabila aplikasi ditutup: Menunggu semua goroutine yang dimulakan oleh mana-mana GoroutineTracker selesai.

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() }
Salin selepas log masuk

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()
}
Salin selepas log masuk

Kesimpulan

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.


Pengarang

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!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!