Go의 병렬 처리 일반 모드

13492 단어 Gorabeetech
내가 WEB의 백엔드 개발에서 Go 언어를 사용하고 싶은 이유 중 하나는 간단하게 실현된 Gooutine을 병행 처리하고 싶다는 것이다.
하지만 고로틴이라고는 하지만 다양한 글씨가 있는데 어떤 게 좋을까요?함정은?기다리고 싶은 건 신경 쓰이는 거야.
그래서 이것은 제 개인적인 견해입니다. 제가 자주 사용하는 모델을 남기고 싶습니다.

패턴


errgroup


Go에 어떤 처리를 적었을 때 적당한 오류 처리는 끊어져도 끊길 수 없다고 생각해서 특수화된errgroup을 사용했습니다.
// サンプル準備
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
newArr := []int{}

// 並列処理を開始
eg := errgroup.Group{}
mutex := sync.Mutex{}
for _, v := range arr {
    // ループする時はちゃんと値をコピーしないと1つが複数回実行されてしまう
    v := v

    eg.Go(func() error {
        // 何かしらの処理(もしエラーが出たらerrを返す)
        fmt.Println(v)

        // 値をsliceやmapに格納する時は排他制御する
        mutex.Lock()
        newArr = append(newArr, v)
        mutex.Unlock()

        time.Sleep(time.Second * 1)
        return nil
    })
}
if err := eg.Wait(); err != nil { // 実行が終わるまで待つ
    // エラーハンドリング
    fmt.Println(err)
    return
}

fmt.Println(newArr)

errgroup (최대 실행수 제어ver)


최대 동시 실행 건수가 예측되지 않을 때는 실행 수를 제한하려 하겠죠.
// サンプル準備
ctx := context.Background()
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
newArr := []int{}

// 並列処理を開始
eg := errgroup.Group{}
mutex := sync.Mutex{}
sem := semaphore.NewWeighted(3) // 最大数を3に設定
for _, v := range arr {
    // ループする時はちゃんと値をコピーしないと1つが複数回実行されてしまう
    v := v

    sem.Acquire(ctx, 1)
    eg.Go(func() error {
        // 何かしらの処理(もしエラーが出たらerrを返す)
        fmt.Println(v)
        time.Sleep(time.Second * 1)

        // 値をsliceやmapに格納する時は排他制御する
        mutex.Lock()
        newArr = append(newArr, v)
        mutex.Unlock()

        sem.Release(1)
        return nil
    })
}
if err := eg.Wait(); err != nil { // 実行が終わるまで待つ
    // エラーハンドリング
    fmt.Println(err)
    return
}

fmt.Println(newArr)

이상은 (^q^)입니다.


응, 없어졌어!
아마 여러분들이 그렇게 생각하실 거예요. 하지만 저는 이 두 개만 사용했어요.
다른 것은 많지만 기본적으로 사용하지 않았다.
  • 채널의 병렬 처리 사용
  • 슬라이스 저장소에서 배타 제어를 잘하면 문제없다
  • 코드의 외관은 직관적이고 알기 쉽다
  • 슬라이스를 사용하면 사이즈도 유연하고 익숙해져서 사용하기 편해요
  • errgroup의context 처리 취소
  • 중도 실패, 병행 실행되고 끝난 처리가 많기 때문에 취소하려고 노력하는 장점이 적다
  • 원래 이렇게 민감한 처리는 병행 처리가 아니었어
  • 어떻게든 하고 싶은 상황에서 스태프를 구축해서 잘 통제하는 게 좋다
  • 주의 사항


    멈춰서 생각해봐요. 정말 병렬 처리가 필요한가요?


    대전제로 병행 처리를 하지 않으면 사용하지 않는 것이 좋다.
    재현성이 낮고 치명적인 버그가 박히기 쉽고 DB 등 예상 외의 부하를 감당하며 코드를 유지하는 엔지니어의 난이도가 높아지는 등이다.
    따라서 5분~10분이면 되기 때문에 일단 멈추고 디자인을 재고해 I/F, 논리, 조회 등에 공을 들여야 한다.
    그걸 바탕으로 유용하다고 판단할 수 있다면 사용하는 게 좋을 것 같아요.

    최대 부하를 잘 상상해 보세요.


    Go는 손쉽게 병행 처리가 가능하지만, 마음이 가볍고 다용도로 쓰일 경우 본격 촬영 환경에서 예상치 못한 부하를 감당하면서 큰 고장이 자주 발생한다.
    나는 주로 아래의 몇 가지를 고려한다.
  • 최대 동시 실행 건수가 얼마나 되는지 대략적으로 계산
  • 다른 고부하 처리타와 맞나요?
  • 최대 동시 실행 시 기계 자원(CPU, 메모리 등)이 가능합니까?
  • 외부 자원(외부 API의 RateLimit, DB)에 대한 부하와 비용은 괜찮습니까?
  • 결과 보존에 주의


    쉽게 잊어버리지만, 슬라이스와 맵을 배타적으로 제어하지 않으면 일치하지 않을 수 있습니다.
    컴파일 오류를 눈치채지 못하면 자신이나 타인의 눈으로 확인하세요.(lint나 정적 분석을 통해 알 수 있나요?)
    mutex := sync.Mutex{}
    
    ...
    
    mutex.Lock()
    newArr = append(newArr, v)
    mutex.Unlock()
    

    총결산


    백엔드를 개발하면 협업자의 API가 느려지고 많은 DB 연결이 필요하며 API의 성능도 떨어진다.
    이럴 때 병행 처리는 문제를 해결하는 카드이기 때문에 적절하게 사용하면 엔지니어 생활을 잘 할 수 있을 것 같아요.

    좋은 웹페이지 즐겨찾기