AI Store에서 AI코딩으로 만들어진 앱을 만나보세요!
지금 바로 방문하기

sync.WaitGroup을 활용한 Go의 동시 작업 조율 전략

카테고리

프로그래밍/소프트웨어 개발

서브카테고리

DevOps

대상자

Go 개발자(1~2년 경력) - 동시 작업 관리와 복잡한 콘커런시 패턴을 다루는 고급 기술을 익히고자 하는 분

핵심 요약

  • sync.WaitGroup의 핵심 메서드
  • Add(delta int): 동시 작업 수를 증가/감소
  • Done(): 작업 완료 시 카운터 감소
  • Wait(): 모든 작업 완료까지 블록
  • 실무 적용 시 주의 사항
  • Done() 호출 과도한 경우 Negative Counter Panic 발생
  • WaitGroup 재사용 시 Reuse Trouble 발생 가능성
  • Goroutine Leak 방지를 위해 defer wg.Done() 필수
  • WaitGroup의 한계 및 대안
  • 의존성 처리: context 또는 channel과 조합 필요
  • 에러 처리: 별도의 에러 채널 추가 추천

섹션별 세부 요약

1. `sync.WaitGroup`의 기본 메커니즘

  • 3가지 핵심 메서드
  • Add(delta int): 작업 수를 delta만큼 증가
  • Done(): Add(-1)을 간접적으로 호출
  • Wait(): 카운터가 0이 될 때까지 블록
  • 간단한 예제

```go

var wg sync.WaitGroup

for i := 0; i < 3; i++ {

wg.Add(1)

go func(id int) {

defer wg.Done()

time.Sleep(time.Second)

fmt.Printf("Worker %d done\n", id)

}(i)

}

wg.Wait()

fmt.Println("All done!")

```

  • 출력 예시

```text

Worker 0 done

Worker 2 done

Worker 1 done

All done!

```

2. `WaitGroup` vs. 다른 콘커런시 도구

  • Mutex와의 차이
  • Mutex는 자원 보호에 초점, WaitGroup은 작업 완료 추적에 초점
  • Channel과의 차이
  • Channel은 데이터 전달과 동기화에 강점, WaitGroup은 단순 작업 동기화에 효율적
  • 비교 테이블

| 도구 | 주요 기능 | 장점 | 단점 |

|------------|-------------------|-------------------|-------------------|

| WaitGroup | 작업 동기화 | 간단하고 빠름 | 데이터 전달 없음 |

| Mutex | 자원 락 관리 | 세부 제어 가능 | 데드락 위험 |

| Channel | 데이터 + 동기화 | 유연한 설계 | 복잡성 증가 |

3. 실무 적용 시 주의 사항

  • Negative Counter Panic 방지
  • Add() 호출 시 반드시 defer wg.Done() 적용
  • Reuse Trouble 방지
  • Wait() 후 재사용 시 WaitGroup 생성 새로 생성
  • Goroutine Leak 방지
  • Done() 누락 시 Wait()가 영원히 대기
  • context와 결합하여 타임아웃 처리

4. 동시 작업 조율(Orchestration)의 핵심

  • 목표
  • 속도: CPU/IO 최적화
  • 순서: 중복/결과 누락 방지
  • 제어: 실시간 조정/에러 처리
  • WaitGroup의 역할
  • 작업 완료 추적만, 의존성/타임아웃은 contextchannel 활용
  • 예제: 파일 다운로드

```go

func downloadFile(id int, wg *sync.WaitGroup) {

defer wg.Done()

time.Sleep(time.Second)

fmt.Printf("File %d downloaded\n", id)

}

```

5. 실무 사례: 배치 작업, API 호출, 크롤러

  • 배치 작업 처리(Worker Pool)
  • 코드 핵심

```go

const maxWorkers = 5

workerChan := make(chan struct{}, maxWorkers)

for i := 1; i <= 100; i++ {

wg.Add(1)

workerChan <- struct{}{}

go func(id int) {

processRecord(id, &wg, results)

<-workerChan

}(i)

}

```

  • 효과
  • 5개의 워커로 리소스 과부하 방지
  • results 채널로 결과 수집
  • Pro Tip: maxWorkersruntime.NumCPU() 기준으로 설정
  • API 병렬 호출 + 타임아웃 처리
  • 코드 핵심

```go

ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)

go callService(ctx, svc, &wg, results)

```

  • 효과
  • 1.5초 타임아웃 설정으로 지연 방지
  • context.Done()으로 실패한 서비스 처리
  • Pro Tip: 에러 채널 별도로 분리하여 정리된 에러 처리
  • 동적 URL 크롤링
  • 코드 핵심

```go

func crawlURL(url string, wg *sync.WaitGroup, results chan<- string) {

defer wg.Done()

time.Sleep(500 * time.Millisecond)

fmt.Printf("Crawled: %s\n", url)

}

```

  • 효과
  • WaitGroup으로 작업 수 추적
  • results 채널로 크롤링 결과 수집

결론

  • 실무 팁
  • WaitGroup은 작업 완료 추적에 최적화됨. 의존성/타임아웃은 contextchannel과 결합하여 처리
  • 워커 풀 사용 시 runtime.NumCPU() 기준으로 maxWorkers 설정
  • 에러 처리를 위해 별도의 에러 채널을 추가할 것
  • defer wg.Done() 사용으로 Goroutine Leak 방지 필수