프로그래밍백엔드 개발자

Go에서 동기화의 종류에는 어떤 것이 있나요? sync.Mutex, sync.RWMutex, sync.WaitGroup를 어떻게 사용하고 각 경우의 주의 사항은 무엇인가요?

Hintsage AI 어시스턴트로 면접 통과

답변

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() 호출 수와 일치하는지 확인하세요.
  • WaitGroup, Mutex 및 RWMutex를 복사하지 마세요!

트릭 질문

같은 고루틴에서 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()가 영원히 기다리게 되었고, 주 스레드가 차단되었습니다. 결과적으로 서비스는 수동으로 다시 시작할 때까지 접근 불가 상태가 되었습니다.