PHpullh
학습 라이브러리/Go/sync 패키지 — Mutex, WaitGroup, Once

GO · 고루틴/채널

sync 패키지 — Mutex, WaitGroup, Once

sync 패키지는 고루틴 간 동기화를 위한 저수준 프리미티브를 제공합니다. Mutex로 임계 영역을 보호하고, WaitGroup으로 완료를 기다리며, Once로 일회성 초기화를 보장합니다.

고루틴/채널중급syncmutexwaitgrouponceconcurrency

핵심 설명

sync 패키지는 고루틴 간 동기화를 위한 저수준 프리미티브를 제공합니다. Mutex로 임계 영역을 보호하고, WaitGroup으로 완료를 기다리며, Once로 일회성 초기화를 보장합니다.

Go code

package main

import (
	"fmt"
	"sync"
	"time"
)

// 1. Mutex: 동시 접근 보호
type SafeCounter struct {
	mu    sync.RWMutex // 읽기-쓰기 뮤텍스
	count map[string]int
}

func NewSafeCounter() *SafeCounter {
	return &SafeCounter{count: make(map[string]int)}
}

func (c *SafeCounter) Increment(key string) {
	c.mu.Lock()         // 쓰기 잠금
	defer c.mu.Unlock()
	c.count[key]++
}

func (c *SafeCounter) Get(key string) int {
	c.mu.RLock()        // 읽기 잠금 (여러 고루틴 동시 읽기 가능)
	defer c.mu.RUnlock()
	return c.count[key]
}

// 2. WaitGroup: 고루틴 완료 대기
func parallelFetch(urls []string) []string {
	var wg sync.WaitGroup
	results := make([]string, len(urls))

	for i, url := range urls {
		wg.Add(1)
		go func(idx int, u string) {
			defer wg.Done()
			time.Sleep(50 * time.Millisecond) // 네트워크 시뮬레이션
			results[idx] = fmt.Sprintf("[%d] %s 완료", idx, u)
		}(i, url)
	}

	wg.Wait() // 모든 고루틴 완료까지 대기
	return results
}

// 3. Once: 정확히 한 번만 실행
type Database struct {
	once     sync.Once
	connPool string
}

func (db *Database) Connect() string {
	db.once.Do(func() {
		fmt.Println("  DB 연결 풀 초기화 (한 번만 실행)")
		time.Sleep(100 * time.Millisecond) // 초기화 시뮬레이션
		db.connPool = "PostgreSQL://pool-ready"
	})
	return db.connPool
}

// 4. Pool: 객체 재사용
var bufferPool = sync.Pool{
	New: func() interface{} {
		fmt.Println("  새 버퍼 생성")
		return make([]byte, 0, 1024)
	},
}

func main() {
	// Mutex 테스트
	fmt.Println("=== SafeCounter ===")
	counter := NewSafeCounter()
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter.Increment("hits")
		}()
	}
	wg.Wait()
	fmt.Printf("hits = %d (기대값: 100)\n", counter.Get("hits"))

	// WaitGroup 테스트
	fmt.Println("\n=== Parallel Fetch ===")
	urls := []string{"api/users", "api/posts", "api/comments"}
	results := parallelFetch(urls)
	for _, r := range results {
		fmt.Println(" ", r)
	}

	// Once 테스트
	fmt.Println("\n=== Singleton DB ===")
	db := &Database{}
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			conn := db.Connect()
			fmt.Printf("  고루틴 %d: %s\n", n, conn)
		}(i)
	}
	wg.Wait()

	// Pool 테스트
	fmt.Println("\n=== Buffer Pool ===")
	buf := bufferPool.Get().([]byte)
	buf = append(buf, "hello pool"...)
	fmt.Printf("  사용: %s\n", buf)
	bufferPool.Put(buf[:0]) // 초기화 후 반환
	buf2 := bufferPool.Get().([]byte) // 재사용 (새로 생성 안 함)
	_ = buf2
}

학습 팁

sync.RWMutex는 읽기가 빈번하고 쓰기가 드문 경우 sync.Mutex보다 성능이 좋습니다. 하지만 단순한 카운터는 sync/atomic 패키지가 더 효율적입니다.

주의할 점

sync.WaitGroupAdd()를 고루틴 안에서 호출하면 Wait()이 먼저 반환될 수 있습니다. 반드시 고루틴 시작 전에 Add()를 호출하세요.

자주 묻는 질문

sync 패키지 — Mutex, WaitGroup, Once란 무엇인가요?

sync 패키지는 고루틴 간 동기화를 위한 저수준 프리미티브를 제공합니다. Mutex 로 임계 영역을 보호하고, WaitGroup 으로 완료를 기다리며, Once 로 일회성 초기화를 보장합니다.

sync 패키지 — Mutex, WaitGroup, Once 학습 시 주의할 점은 무엇인가요?

sync.WaitGroup 의 Add() 를 고루틴 안에서 호출하면 Wait() 이 먼저 반환될 수 있습니다. 반드시 고루틴 시작 전에 Add() 를 호출하세요.