>  기사  >  백엔드 개발  >  리팩토링: 불필요하게 Reflect를 사용하는 GoroutineTracker

리팩토링: 불필요하게 Reflect를 사용하는 GoroutineTracker

PHPz
PHPz원래의
2024-07-17 04:26:29739검색

Refactor: GoroutineTracker with unnecessary usage of reflect

오늘 회사 코드베이스에서 이 코드를 발견했습니다(코드와 주석은 데모용으로 다시 작성되었으며 독점 ​​코드는 포함되지 않습니다):

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는 고루틴 수, 각 고루틴에 소요되는 시간 등 코드베이스에서 고루틴의 사용을 추적하는 데 사용됩니다. Go 메서드는 새 고루틴을 시작하고 추적하는 데 사용됩니다. Wait 메소드는 모든 고루틴이 완료될 때까지 기다리는 데 사용됩니다.

사용예:

g := NewGoroutineTracker()
g.Go(ctx, "task1", doTask1, arg1, arg2)
g.Go(ctx, "task2", doTask2, arg3)
g.Wait()

문제: Reflect의 사용은 불필요하며 피할 수 있습니다.

글쎄, 해당 코드는 작동하지만 Reflect 패키지를 사용하여 함수 서명을 확인한 다음 함수를 호출합니다. 전혀 필요하지 않으며 다음과 같이 사용법을 변경하면 이를 방지할 수 있습니다.

g := NewGoroutineTracker()
g.Go(ctx, "task1", func() error {
    return doTask1(arg1, arg2)
})
g.Go(ctx, "task2", func() error {
    return doTask2(arg3)
})

새 코드는 더 간단해지고 많은 이점을 제공합니다.

  • 유형 안전성: Reflect를 사용하여 함수 서명을 확인할 필요가 없습니다. 컴파일러가 우리를 위해 그 일을 해줄 것입니다. 함수 서명이 인수와 일치하지 않으면 원본 코드에 잠재적인 런타임 오류가 있을 수 있습니다.
  • 오류 처리: 함수에서 오류를 반환하고 호출자에서 이를 처리할 수 있습니다. 원본 코드는 함수의 출력을 무시합니다.
  • 가독성: 새 코드는 더 읽기 쉽고 이해하기 쉽습니다. 코드에서 직접 함수 시그니처와 인수를 볼 수 있습니다.

GoroutineTracker의 더 나은 구현

리팩터링된 코드는 다음과 같습니다.

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의 또 다른 사용 사례는 애플리케이션을 종료하기 전에 모든 고루틴이 완료되기를 기다리는 것입니다. 따라서 우리는 두 가지 유형의 기다림을 가질 수 있습니다:

  • 함수: 모든 로컬 고루틴이 완료되기를 기다리고 있습니다.
  • 애플리케이션 종료 시: GoroutineTracker에 의해 시작된 모든 고루틴이 완료되기를 기다립니다.

글로벌 트래커를 추가하고 모든 트래커가 글로벌 트래커에 해당 기능을 등록하도록 하여 이를 구현할 수 있습니다.

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()을 사용하여 모든 고루틴이 완료될 때까지 기다릴 수 있습니다.

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의 원래 구현은 작동하고 고루틴을 추적할 수 있지만, 반영 패키지를 사용하여 함수를 동적으로 확인하고 호출하면 불필요한 복잡성과 잠재적인 런타임 오류가 발생합니다. 함수 리터럴을 직접 허용하도록 코드를 리팩터링함으로써 유형 안전성이 향상되고 오류 처리가 간소화되며 가독성이 향상되었습니다. 이 접근 방식은 Go의 컴파일러 검사 유형 시스템을 활용하여 함수 서명과 인수 간의 호환성을 보장함으로써 더욱 강력하고 유지 관리하기 쉬운 코드를 만듭니다. 이러한 변경 사항을 채택함으로써 우리는 Go 프로그래밍의 모범 사례에 맞춰 명확성과 신뢰성을 위해 GoroutineTracker를 최적화했습니다.


작가

올리버 응우옌입니다. 주로 Go와 JavaScript를 다루는 소프트웨어 제조업체입니다. 나는 매일 더 나은 모습을 배우고 보는 것을 즐깁니다. 때때로 새로운 오픈 소스 프로젝트를 시작하세요. 여행 중에 지식과 생각을 공유해보세요.

위 내용은 리팩토링: 불필요하게 Reflect를 사용하는 GoroutineTracker의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.