Go에서는 경쟁하는 고루틴의 동기화를 위해 sync 패키지의 구조체를 사용하며, 가장 일반적으로 사용되는 것은 sync.Mutex, sync.RWMutex, sync.WaitGroup입니다.
**sync.Mutex**는 공유 데이터에 대한 접근 시 상호 배제를 위한 메커니즘을 제공합니다. 이의 메소드는 Lock() (잠금)과 Unlock() (해제)입니다.
**sync.RWMutex**는 일반 뮤텍스의 기능을 확장하여 병렬 읽기를 허용하지만, 독점적인 수정을 요구합니다.
**sync.WaitGroup**는 고루틴 그룹의 완료를 기다리는 데 사용됩니다. Add(int), Done(), Wait() 메소드를 통해 병렬 작업의 생명주기를 관리합니다.
예:
var mu sync.RWMutex data := 0 // 읽기 mu.RLock() fmt.Println(data) mu.RUnlock() // 쓰기 mu.Lock() data = 42 mu.Unlock()
주의 사항:
defer 블록에서 Unlock을 사용하여 패닉이 발생하더라도 뮤텍스를 잊지 않고 해제하도록 하세요.WaitGroup.Done() 호출 수가 Add() 호출 수와 일치하는지 확인하세요.같은 고루틴에서 sync.Mutex (또는 RWMutex)를 두 번 연속으로 잠글 수 있나요? 무슨 일이 일어날까요?
답변: 아닙니다. 같은 Mutex에서 중간에 Unlock() 없이 두 번 연속으로 Lock()을 호출하면 고루틴이 영원히 차단됩니다 (교착 상태). Go에서 뮤텍스는 재진입이 불가능합니다.
예:
var mu sync.Mutex mu.Lock() // ... mu.Lock() // 교착 상태: 같은 스레드가 이미 잠금을 가지고 있어 영원히 차단됩니다.
사례
하이로드 REST API 프로젝트에서 개발자는 모든 요청 처리를 하나의 뮤텍스로 감쌌습니다. 이로 인해 성능이 급격히 저하되었습니다. 동시에 하나의 요청만 처리될 수 있었고, 수천 고객을 처리할 수 있는 계획이 있었습니다. 원인은 Mutex와 RWMutex의 차이를 모른 것과 병렬 읽기를 무시한 것이었습니다.
사례
Mutex가 포함된 구조체를 복사할 때 팀의 한 구성원이 실수로 복사본을 다른 함수로 전달했습니다. 이로 인해 "sync: copy of sync.Mutex"라는 패닉 메시지가 발생하고 높은 부하에서 프로덕션에서 크래시가 발생했습니다.
사례
WaitGroup을 사용할 때 몇몇 고루틴에서 Done() 호출을 잊어버려 Wait()가 영원히 기다리게 되었고, 주 스레드가 차단되었습니다. 결과적으로 서비스는 수동으로 다시 시작할 때까지 접근 불가 상태가 되었습니다.