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.WaitGroup의 Add()를 고루틴 안에서 호출하면 Wait()이 먼저 반환될 수 있습니다. 반드시 고루틴 시작 전에 Add()를 호출하세요.
자주 묻는 질문
sync 패키지 — Mutex, WaitGroup, Once란 무엇인가요?
sync 패키지는 고루틴 간 동기화를 위한 저수준 프리미티브를 제공합니다. Mutex 로 임계 영역을 보호하고, WaitGroup 으로 완료를 기다리며, Once 로 일회성 초기화를 보장합니다.
sync 패키지 — Mutex, WaitGroup, Once 학습 시 주의할 점은 무엇인가요?
sync.WaitGroup 의 Add() 를 고루틴 안에서 호출하면 Wait() 이 먼저 반환될 수 있습니다. 반드시 고루틴 시작 전에 Add() 를 호출하세요.
Continue Learning
Go 학습을 이어가세요
총 200개의 독립 HTML 학습 문서 중 하나입니다. 각 문서는 고유 URL과 canonical 메타데이터를 가집니다.