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의 역할
- 작업 완료 추적만, 의존성/타임아웃은
context
와channel
활용 - 예제: 파일 다운로드
```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:
maxWorkers
는runtime.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
은 작업 완료 추적에 최적화됨. 의존성/타임아웃은context
와channel
과 결합하여 처리- 워커 풀 사용 시
runtime.NumCPU()
기준으로maxWorkers
설정 - 에러 처리를 위해 별도의 에러 채널을 추가할 것
defer wg.Done()
사용으로Goroutine Leak
방지 필수