Menu
Coddy logo textTech

sync.WaitGroup

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 64번째.

Goroutines Basics 레슨에서는 고루틴이 끝날 때까지 기다리기 위해 time.Sleep을 사용했습니다. 이는 취약한 접근 방식입니다. sync.WaitGroup은 여러 고루틴이 작업을 complete할 때까지 기다리는 적절한 방법을 제공합니다.

WaitGroup은 counter처럼 작동합니다. goroutine을 시작하기 before increment하고, goroutine이 완료되면 decrement하며, counter가 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만큼 감소시키는 Done()(Add(-1)과 동일), 그리고 카운터가 0에 도달할 때까지 차단하는 Wait()입니다. defer wg.Done()을 사용하면 goroutine에서 패닉이 발생하더라도 카운터가 감소합니다.

일반적인 실수는 Add를 goroutine 안에서 호출하고 그 전에 호출하지 않는 것입니다. 이렇게 하면 모든 goroutine이 등록되기 전에 Wait가 반환될 수 있는 경쟁 condition이 발생합니다:

// 잘못됨 - 레이스 컨디션
go func() {
    wg.Add(1)  // Wait() 이후에 실행될 수 있음
    defer wg.Done()
}()

// 올바름 - 실행 전에 Add
wg.Add(1)
go func() {
    defer wg.Done()
}()

WaitGroups는 concurrent 작업을 조정하기 위해 구조체에 자주 포함되므로, Go에서 thread-safe 타입을 구축하는 데 필수적입니다.

challenge icon

챌린지

쉬움

sync.WaitGroup를 사용하여 여러 concurrent file 다운로드를 조정하는 download manager를 만들어 봅시다. manager는 임의의 sleep timer에 의존하지 않고 모든 다운로드가 complete되는 시점을 추적합니다.

코드를 두 개의 file에 구성합니다:

  • downloader.go: 다운로드 조정 로직을 정의합니다.

    ID(int), Filename(string), Size(int, KB를 나타냄) 필드를 포함하는 Download struct를 Create합니다.

    DownloadProcess(wg *sync.WaitGroup) method를 Implement합니다. 이 method는 file size를 기준으로 한 Duration 동안 sleep하여 다운로드를 시뮬레이션합니다(Size * 10 milliseconds 사용). 다운로드가 완료되면 Downloaded: [Filename] ([Size]KB)를 출력합니다. WaitGroup counter가 올바르게 decrement되도록 defer wg.Done()을 사용합니다.

    모든 다운로드를 goroutines를 사용하여 concurrently launch하고 모두 complete될 때까지 기다리는 StartDownloads(downloads []Download) int function을 Create합니다. 각 goroutine을 launch하기 전에 wg.Add(1)을 Call하고, goroutine 내부에서는 Call하지 않도록 주의하세요. 모든 다운로드의 전체 size를 합산한 값을 return합니다.

  • main.go: 다운로드 information을 읽고 concurrent 다운로드를 조정합니다.

    다운로드 수를 읽은 다음, 각 다운로드에 대해 ID, filename, size를 읽습니다. 다운로드를 Create하고 StartDownloads에 전달합니다. 모든 다운로드가 complete된 후 All downloads complete: [total]KB total을 출력합니다.

다음 input이 제공됩니다:

  • Line 1: 다운로드 수(integer)
  • 이후 lines: 각 다운로드에 대해 세 lines - ID(integer), filename(string), size(KB 단위의 integer)

예를 들어 다음이 주어졌을 때:

3
1
report.pdf
20
2
image.png
5
3
data.csv
10

출력에는 다운로드가 complete되는 순서가 표시되어야 합니다(더 작은 file은 sleep time이 더 짧으므로 먼저 완료됨). 그 다음 summary가 표시됩니다:

Downloaded: image.png (5KB)
Downloaded: data.csv (10KB)
Downloaded: report.pdf (20KB)
All downloads complete: 35KB total

time.Sleep을 사용하는 것과의 주요 차이점은 wg.Wait()가 모든 goroutines가 Done()으로 completion을 signal할 때까지 block된다는 점입니다. 따라서 각 다운로드에 얼마나 오래 걸리는지와 관계없이 정밀한 synchronization을 제공합니다.

직접 해보기

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: Print the summary: "All downloads complete: [total]KB total"
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 Go 컴파일러