errgroup이 있는 고루틴에서 오류 처리

6316 단어
Golang 고루틴으로 삶을 더 쉽게 만들었지만 때로는 고루틴 내부에서 발생한 오류를 효과적으로 처리하기가 어렵습니다. 예를 들어, 어떤 종류의 작업 배열이 있고 각각에 대해 특정 기능을 실행하려고 한다고 상상해 보십시오. 반면에 오류가 발생하면 해당 오류를 상위 기능으로 전파하려고 합니다.

코드에서 설명하겠습니다. runners 집합이 있고 각 러너에 Handle 함수가 있다고 상상해 보겠습니다.

type HandlerFunc func(input string) error
type Runner struct {
    Name string
    Handle HandlerFunc
}



또한 Runners 라는 다른 유형을 정의하고 싶습니다. 러너 배열을 둘러싼 단순한 래퍼입니다.

type Runners []Runner



따라서 다음과 같이 모든 러너를 통해 실행되는 함수를 정의할 수 있습니다.

func (r Runners) Execute() error {
    for _, runner := range r {
        if err := runner.Handle(runner.Name); err != nil {
            return err
        }
    }
    return nil
}



마지막으로 일부 러너를 정의하고 실행합니다.

func main() {
    runners := Runners{
        Runner{
            Name: "1",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
        Runner{
            Name: "2",
            Handle: func(input string) error {
                return fmt.Errorf("something bad happened in runner [%s]", input)
            },
        },
        Runner{
            Name: "3",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
    }

    err := runners.Execute()
    if err != nil {
        fmt.Printf("execution failed: %v", err)
    }  
}



이 코드를 실행하면 runner 1 is running 가 출력됩니다. 문제는 우리가 세 번째 주자를 뛰지 않았다는 것입니다. 따라서 이 경우 오류를 인쇄하고 실행을 계속 실행하지만 오류를 상위 함수로 전파할 수 없습니다!

func (r Runners) Execute() error {
    for _, runner := range r {
        if err := runner.Handle(runner.Name); err != nil {
      fmt.Printf("error happened in runner [%s]: %v", runner.Name, err)
        }
    }
    return nil
}



이제 완전히 동일한 시나리오로 다른 고루틴에서 각 러너를 실행하려면 어떻게 해야 할까요? 코드를 약간 변경하여 작동하는지 봅시다.

먼저 함수 호출 뒤에 go를 추가합니다. 따라서 Execute 함수는 다음과 같아야 합니다.

func (r Runners) Execute() error {
    for _, runner := range r {
        go func(runner Runner) error {
            if err := runner.Handle(runner.Name); err != nil {
                return err
            }
            return nil
        }(runner)
    }
    return nil
}



그러나 우리가 프로그램을 다시 실행하면 알겠지만, 고루틴이 작업을 마칠 때까지 기다리라고 애플리케이션에 말하지 않았기 때문에 아무 것도 출력되지 않을 것입니다. 단순화를 위해 Sleep 함수에 main를 추가해 보겠습니다.

err := runners.Execute()
time.Sleep(3 * time.Second)
if err != nil {
    fmt.Printf("execution failed: %v", err)
}



이제 다시 실행해 보겠습니다. 이번에는 출력이 다음과 같아야 합니다.

runner 1 is running
runner 3 is running



괜찮아, 안돼! 실행은 러너 1과 3을 실행할 수 있었기 때문에 잘 작동했지만 여전히 오류에 대해 아무 조치도 취하지 않았습니다.

errgroup에 오신 것을 환영합니다



이제 errgroup 으로 문제를 해결할 시간입니다. 정말 간단하고 쉽습니다. 동기화 패키지에서 waitgroups처럼 작동합니다. 솔직히 말해서, 그것은 뒤에서 대기 그룹을 사용하고 있지만 언급된 시나리오는 매우 일반적이기 때문에 errgroup은 우리의 삶을 더 쉽게 만듭니다!

대기 그룹에 대해 잘 알고 있다면 wg.Done()wg.Wait()가 무엇을 의미하는지 알아야 합니다. errgroup 같은 것을 제공합니다. 코드에서 명확하게 합시다. 먼저 g 변수를 선언합니다.

g := new(errgroup.Group)


Go func를 사용하여 고루틴 내에서 실행 함수를 실행합니다. 따라서 Execute 함수는 다음과 같이 바뀝니다.

func (r Runners) Execute() {
    for _, runner := range r {
        rx := runner
        g.Go(func() error {
            return rx.Handle(rx.Name)
        })
    }
}


Go 함수는 오류를 반환하고 func 오류를 반환합니다. 이 오류가 nil가 아니면 Wait 함수의 반환에 해당 오류가 있습니다.

다음은 Wait 함수입니다.

runners.Execute()
err := g.Wait()
if err != nil {
    fmt.Printf("execution failed: %v", err)
}



보다시피 Wait 함수는 마지막 고루틴이 결과를 얻고 발생한 첫 번째 오류를 반환할 때까지 기다리기 때문에 더 이상 time.Sleep()가 필요하지 않습니다. 모든 함수가 오류 없이 실행되면 반환nil
출력은 다음과 같습니다.

runner 3 is running
runner 1 is running
execution failed: something bad happened in runner [2]



전체 코드는 다음과 같습니다.

package main

import (
    "fmt"

    "golang.org/x/sync/errgroup"
)

var g errgroup.Group

type HandlerFunc func(input string) error

type Runner struct {
    Name string
    Handle HandlerFunc
}

type Runners []Runner

func (r Runners) Execute() {

    for _, runner := range r {
        rx := runner
        g.Go(func() error {
            return rx.Handle(rx.Name)
        })
    }
}

func main() {
    runners := Runners{
        Runner{
            Name: "1",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
        Runner{
            Name: "2",
            Handle: func(input string) error {
                return fmt.Errorf("something bad happened in runner [%s]", input)
            },
        },
        Runner{
            Name: "3",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
    }
    runners.Execute()
    err := g.Wait()
    if err != nil {
        fmt.Printf("execution failed: %v", err)
    }
}

좋은 웹페이지 즐겨찾기