sync.WaitGroup
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 64번째.
Goroutines Basics 레슨에서, 우리는 고루틴이 끝나기를 기다리기 위해 time.Sleep을 사용했습니다. 이는 취약한 방식입니다. sync.WaitGroup은 여러 고루틴이 작업을 완료할 때까지 기다리는 적절한 방법을 제공합니다.
WaitGroup은 카운터처럼 작동합니다. 고루틴(goroutine)을 시작하기 전에 이를 증가시키고, 고루틴이 종료될 때 이를 감소시키며, 카운터가 0이 될 때까지 대기(block)합니다:
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 카운터 증가
go func(id int) {
defer wg.Done() // 완료 시 감소
fmt.Printf("Worker %d finished\n", id)
}(i)
}
wg.Wait() // 카운터가 0이 될 때까지 대기
fmt.Println("All workers complete")
}세 가지 주요 메서드는 카운터를 n만큼 증가시키는 Add(n), 카운터를 1만큼 감소시키는(Add(-1)과 동일) Done(), 그리고 카운터가 0이 될 때까지 대기(block)하는 Wait()입니다. defer wg.Done()을 사용하면 고루틴(goroutine)이 패닉(panic)을 일으키더라도 카운터가 감소하도록 보장합니다.
흔한 실수는 Add를 고루틴 이전이 아닌 고루틴 내부에서 호출하는 것입니다. 이는 모든 고루틴이 등록되기 전에 Wait가 반환될 수 있는 경합 조건(race condition)을 발생시킵니다:
// 잘못됨 - 경쟁 상태(race condition)
go func() {
wg.Add(1) // Wait() 이후에 실행될 수 있음
defer wg.Done()
}()
// 올바름 - 실행 전에 Add 호출
wg.Add(1)
go func() {
defer wg.Done()
}()WaitGroups는 종종 동시성 작업을 조정하기 위해 구조체에 임베드되며, 이는 Go에서 스레드 안전한 타입을 구축하는 데 필수적입니다.
챌린지
쉬움sync.WaitGroup을 사용하여 여러 개의 동시 파일 다운로드를 조정하는 다운로드 관리자를 만들어 보겠습니다. 이 관리자는 임의의 대기 시간(sleep timers)에 의존하지 않고 모든 다운로드가 완료되는 시점을 추적합니다.
코드는 두 개의 파일로 구성됩니다:
downloader.go: 다운로드 조정 로직을 정의합니다.ID(int),Filename(string),Size(int, KB 단위) 필드를 가진Download구조체를 생성하세요.Download에Process(wg *sync.WaitGroup)메서드를 구현하세요. 이 메서드는 파일 크기에 기반한 시간(Size * 10밀리초) 동안 대기하여 다운로드를 시뮬레이션합니다. 다운로드가 완료되면Downloaded: [Filename] ([Size]KB)를 출력합니다. WaitGroup 카운터가 제대로 감소하도록defer wg.Done()을 사용하세요.고루틴(goroutines)을 사용하여 모든 다운로드를 동시에 실행하고 모두 완료될 때까지 기다리는
StartDownloads(downloads []Download) int함수를 생성하세요. 각 고루틴을 실행하기 전(내부가 아님)에wg.Add(1)을 호출해야 함을 기억하세요. 모든 다운로드 크기의 합계를 반환하세요.main.go: 다운로드 정보를 읽고 동시 다운로드를 조율합니다.다운로드 횟수를 읽은 다음, 각 다운로드에 대해 ID, 파일 이름, 크기를 읽습니다. 다운로드 목록을 생성하고
StartDownloads에 전달하세요. 모든 다운로드가 완료된 후All downloads complete: [total]KB total을 출력하세요.
다음과 같은 입력이 제공됩니다:
- 1행: 다운로드 횟수 (정수)
- 다음 행들: 각 다운로드당 세 줄 - ID (정수), 파일 이름 (문자열), KB 단위 크기 (정수)
예를 들어, 다음과 같이 입력되면:
3
1
report.pdf
20
2
image.png
5
3
data.csv
10출력에는 다운로드 완료 메시지(대기 시간이 짧은 작은 파일이 먼저 완료됨)와 요약 메시지가 표시되어야 합니다:
Downloaded: image.png (5KB)
Downloaded: data.csv (10KB)
Downloaded: report.pdf (20KB)
All downloads complete: 35KB total단순히 time.Sleep을 사용하는 것과 다른 핵심적인 차이점은, wg.Wait()가 모든 고루틴이 Done()으로 완료 신호를 보낼 때까지 차단(block)된다는 것입니다. 이를 통해 각 다운로드에 걸리는 시간과 관계없이 정밀한 동기화가 가능합니다.
치트 시트
sync.WaitGroup은 고루틴(goroutine)이 완료될 때까지 기다리는 적절한 방법을 제공합니다. 이는 실행 중인 고루틴을 추적하는 카운터처럼 작동합니다.
세 가지 주요 메서드는 다음과 같습니다:
Add(n)- 카운터를 n만큼 증가시킵니다.Done()- 카운터를 1만큼 감소시킵니다 (Add(-1)과 동일).Wait()- 카운터가 0이 될 때까지 블록(대기)합니다.
기본 사용 패턴:
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 고루틴을 시작하기 전에 증가시킴
go func(id int) {
defer wg.Done() // 완료 시 감소시킴
fmt.Printf("Worker %d finished\n", id)
}(i)
}
wg.Wait() // 모든 고루틴이 완료될 때까지 블록
fmt.Println("All workers complete")defer wg.Done()을 사용하면 고루틴에서 패닉(panic)이 발생하더라도 카운터가 감소하도록 보장할 수 있습니다.
중요: 레이스 컨디션(race condition)을 피하기 위해 Add는 항상 고루틴 내부가 아니라 고루틴을 실행하기 전에 호출해야 합니다:
// 잘못된 예 - 레이스 컨디션 발생
go func() {
wg.Add(1) // Wait() 이후에 실행될 수 있음
defer wg.Done()
}()
// 올바른 예
wg.Add(1)
go func() {
defer wg.Done()
}()직접 해보기
package main
import (
"fmt"
)
func main() {
// 다운로드 수 읽기
var n int
fmt.Scanln(&n)
// 다운로드 정보 읽기
downloads := make([]Download, n)
for i := 0; i < n; i++ {
var id int
var filename string
var size int
fmt.Scanln(&id)
fmt.Scanln(&filename)
fmt.Scanln(&size)
downloads[i] = Download{
ID: id,
Filename: filename,
Size: size,
}
}
// TODO: downloads 슬라이스를 사용하여 StartDownloads 호출
// TODO: 요약 출력: "All downloads complete: [total]KB total"
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서