← 가이드 목록으로 ← Back to guides

Go 동시성 프로그래밍과 실전 패턴 완벽 가이드 Go Concurrency Programming & Practical Patterns Guide

Go의 기본 문법을 넘어 실무에서 필요한 고급 동시성 패턴을 다룹니다. Worker Pool, Fan-out/Fan-in, Context를 활용한 취소 처리, 에러 그룹 등 프로덕션 레벨 Go 코드를 작성하는 방법을 배웁니다.

Beyond Go basics, this covers advanced concurrency patterns needed in practice. Learn to write production-level Go code with Worker Pool, Fan-out/Fan-in, cancellation with Context, and error groups.

Worker Pool 패턴 Worker Pool Pattern

대량의 작업을 효율적으로 처리하기 위해 고정된 수의 워커 고루틴을 사용합니다. 리소스 사용을 제한하면서도 병렬 처리의 이점을 얻을 수 있습니다.

Use a fixed number of worker goroutines to efficiently process large amounts of work. You get the benefits of parallel processing while limiting resource usage.

func workerPool(numWorkers int, jobs chan int, results chan int) {
  var wg sync.WaitGroup

  // 워커 생성
  for i := 0; i < numWorkers; i++ {
    wg.Add(1)
    go func(id int) {
      defer wg.Done()
      for job := range jobs {
        results <- process(job)
      }
    }(i)
  }

  wg.Wait()
  close(results)
}
func workerPool(numWorkers int, jobs chan int, results chan int) {
  var wg sync.WaitGroup

  // Create workers
  for i := 0; i < numWorkers; i++ {
    wg.Add(1)
    go func(id int) {
      defer wg.Done()
      for job := range jobs {
        results <- process(job)
      }
    }(i)
  }

  wg.Wait()
  close(results)
}

Context로 취소 처리 Cancellation with Context

context.Context는 Go에서 취소 신호, 타임아웃, 값 전달을 위한 표준 방법입니다. HTTP 요청, 데이터베이스 쿼리 등 모든 I/O 작업에 사용해야 합니다.

context.Context is Go's standard way to carry cancellation signals, timeouts, and values. It should be used for all I/O operations like HTTP requests and database queries.

// 타임아웃이 있는 컨텍스트
ctx, cancel := context.WithTimeout(
  context.Background(),
  5*time.Second,
)
defer cancel()

// HTTP 요청에 컨텍스트 사용
req, _ := http.NewRequestWithContext(
  ctx, "GET", url, nil,
)
resp, err := client.Do(req)

// 취소 확인
select {
case <-ctx.Done():
  return ctx.Err()
case result := <-resultCh:
  return result
}
// Context with timeout
ctx, cancel := context.WithTimeout(
  context.Background(),
  5*time.Second,
)
defer cancel()

// Use context for HTTP request
req, _ := http.NewRequestWithContext(
  ctx, "GET", url, nil,
)
resp, err := client.Do(req)

// Check for cancellation
select {
case <-ctx.Done():
  return ctx.Err()
case result := <-resultCh:
  return result
}

📊 Context 종류 📊 Context Types

  • context.Background(): 최상위 컨텍스트, main에서 시작
  • context.Background(): Top-level context, starts from main
  • context.WithCancel: 수동 취소 가능
  • context.WithCancel: Manual cancellation
  • context.WithTimeout: 지정 시간 후 자동 취소
  • context.WithTimeout: Auto-cancel after duration
  • context.WithDeadline: 특정 시점에 자동 취소
  • context.WithDeadline: Auto-cancel at specific time

에러 그룹 (errgroup) Error Groups (errgroup)

여러 고루틴에서 발생하는 에러를 깔끔하게 처리하려면 golang.org/x/sync/errgroup을 사용합니다.

Use golang.org/x/sync/errgroup to cleanly handle errors from multiple goroutines.

import "golang.org/x/sync/errgroup"

func fetchAll(urls []string) error {
  g, ctx := errgroup.WithContext(context.Background())

  for _, url := range urls {
    url := url // 클로저용 복사
    g.Go(func() error {
      return fetch(ctx, url)
    })
  }

  // 모든 고루틴 완료 대기, 첫 에러 반환
  return g.Wait()
}
import "golang.org/x/sync/errgroup"

func fetchAll(urls []string) error {
  g, ctx := errgroup.WithContext(context.Background())

  for _, url := range urls {
    url := url // Copy for closure
    g.Go(func() error {
      return fetch(ctx, url)
    })
  }

  // Wait for all goroutines, return first error
  return g.Wait()
}

Select 패턴 활용 Select Pattern Usage

// 타임아웃과 함께 여러 채널 대기
select {
case msg := <-msgCh:
  fmt.Println("메시지:", msg)
case err := <-errCh:
  return err
case <-time.After(10 * time.Second):
  return errors.New("타임아웃")
case <-ctx.Done():
  return ctx.Err()
}
// Wait on multiple channels with timeout
select {
case msg := <-msgCh:
  fmt.Println("Message:", msg)
case err := <-errCh:
  return err
case <-time.After(10 * time.Second):
  return errors.New("timeout")
case <-ctx.Done():
  return ctx.Err()
}

테이블 기반 테스트 Table-Driven Tests

Go에서 권장하는 테스트 작성 패턴입니다. 여러 케이스를 깔끔하게 정리할 수 있습니다.

This is Go's recommended testing pattern. It organizes multiple cases cleanly.

func TestAdd(t *testing.T) {
  tests := []struct {
    name string
    a, b int
    want int
  }{
    {"양수", 1, 2, 3},
    {"음수", -1, -2, -3},
    {"영", 0, 0, 0},
  }

  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      if got := Add(tt.a, tt.b); got != tt.want {
        t.Errorf("Add() = %v, want %v", got, tt.want)
      }
    })
  }
}
func TestAdd(t *testing.T) {
  tests := []struct {
    name string
    a, b int
    want int
  }{
    {"positive", 1, 2, 3},
    {"negative", -1, -2, -3},
    {"zero", 0, 0, 0},
  }

  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      if got := Add(tt.a, tt.b); got != tt.want {
        t.Errorf("Add() = %v, want %v", got, tt.want)
      }
    })
  }
}

💡 Go 동시성 타자 연습 팁 💡 Go Concurrency Typing Tips

go func(), chan, select, ctx.Done() 패턴을 자주 타이핑해보세요. 동시성 코드는 복잡해 보이지만, 패턴을 체화하면 빠르게 작성할 수 있습니다.

Practice typing go func(), chan, select, ctx.Done() patterns often. Concurrent code looks complex, but once you internalize the patterns, you can write them quickly.

⚠️ 고루틴 누수 방지 ⚠️ Preventing Goroutine Leaks

고루틴이 영원히 블록되면 메모리 누수가 발생합니다. 항상 context를 사용하거나 채널을 닫아 고루틴이 종료될 수 있게 하세요.

Goroutines that block forever cause memory leaks. Always use context or close channels to ensure goroutines can exit.