Quick Comparison
Go 동시성은 channel만으로 끝나지 않습니다. 작업 종료 대기와 공유 상태 보호는 sync 패키지로 명확히 표현할 때가 많습니다.
| 표면 | 역할 |
|---|---|
WaitGroup | 여러 goroutine이 끝날 때까지 기다림 |
Mutex | 공유 상태를 한 번에 하나만 접근하게 보호 |
RWMutex | 읽기는 여러 개, 쓰기는 하나로 제한 |
atomic | 아주 작은 값의 원자적 읽기/쓰기 |
| channel | 값 전달과 소유권 이동 |
기본 흐름
WaitGroup은 종료 대기용이다
var wg sync.WaitGroup
for _, job := range jobs {
wg.Add(1)
go func(job Job) {
defer wg.Done()
process(job)
}(job)
}
wg.Wait()WaitGroup은 결과 전달 도구가 아니라 goroutine 종료를 기다리는 도구입니다. 에러나 결과는 channel, mutex로 보호한 변수, 별도 그룹 패턴으로 전달합니다.
Go 1.25 이상에서는 Add와 go 호출을 묶는 WaitGroup.Go도 사용할 수 있습니다.
var wg sync.WaitGroup
for _, job := range jobs {
job := job
wg.Go(func() {
process(job)
})
}
wg.Wait()WaitGroup.Go에 넘긴 함수는 panic하면 안 됩니다. 빈 WaitGroup에서 새 작업을 시작할 때는 Go 또는 양수 Add가 Wait보다 먼저 와야 하고, 독립된 다음 작업 묶음에 재사용할 때는 이전 Wait가 돌아온 뒤에 시작합니다.
Mutex는 공유 상태를 보호한다
var mu sync.Mutex
counts := map[string]int{}
mu.Lock()
counts[key]++
mu.Unlock()여러 goroutine이 같은 map이나 struct를 동시에 바꾸면 data race가 생길 수 있습니다. 공유 상태는 mutex로 보호하거나, channel로 소유권을 한 goroutine에 모읍니다.
defer Unlock으로 해제를 보장한다
mu.Lock()
defer mu.Unlock()
counts[key]++함수 안에서 lock을 잡는 경우 defer로 unlock을 보장하면 중간 return이나 panic 흐름에서도 해제가 빠질 가능성이 줄어듭니다. Mutex와 WaitGroup은 zero value부터 쓸 수 있지만, 처음 사용한 뒤 복사하면 안 됩니다. defer mu.Unlock()은 함수가 끝날 때까지 lock을 유지하므로, 반복문 한 바퀴마다 짧게 보호해야 하는 코드에서는 작은 helper 함수로 lock 범위를 감싸거나 명시적으로 Unlock합니다.
RWMutex와 atomic은 좁은 조건에서 쓴다
var rw sync.RWMutex
rw.RLock()
value := cache[key]
rw.RUnlock()RWMutex는 여러 reader 또는 하나의 writer만 허용합니다. 읽기가 많다는 이유만으로 바꾸지 말고, 측정한 lock contention이 있을 때 검토합니다. 단일 counter나 flag는 atomic.Int64, atomic.Bool로 처리할 수 있지만 여러 field의 상태를 함께 맞춰야 하면 Mutex가 더 안전합니다. typed atomic 값도 첫 사용 뒤 복사하지 않습니다.
선택 기준
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| goroutine 종료만 기다림 | sync.WaitGroup |
| map/struct 공유 수정 | sync.Mutex |
| 읽기가 훨씬 많고 쓰기가 적음 | sync.RWMutex 검토 |
| counter나 flag 같은 작은 값 | sync/atomic 검토 |
| 작업을 전달하고 소유권을 넘김 | channel |
| 취소 전파 | context.Context |
동시성 설계는 "channel 또는 mutex"의 취향 문제가 아니라, 값의 소유권이 이동하는지 공유 상태를 보호하는지의 문제입니다.
주의할 점
WaitGroup.Add는 goroutine 안이 아니라 시작 전에 호출하는 편이 안전합니다. WaitGroup은 취소·오류 전파·동시 실행 수 제한을 제공하지 않으며 종료만 기다립니다. Mutex를 복사하거나 lock/unlock 경로가 어긋나면 찾기 어려운 버그가 됩니다. data race는 테스트에서 항상 터지지 않으므로 공유 상태가 있으면 race detector로 확인하는 습관이 중요합니다.
참고 링크
3 sources