🔥 sync.Mutex로 동시성 문제 해결하기
채널을 사용하여 고루틴 간의 통신을 할 수 있다는 걸 배웠어요. 그런데 만약 통신이 필요하지 않다면 어떨까요? 단순히 변수에 동시에 접근하는 것을 막아서 충돌을 피하고 싶다면 말이에요.
이런 개념을 *상호 배제(Mutual Exclusion)*라고 하며, 이를 제공하는 데이터 구조를 관례적으로 *뮤텍스(Mutex)*라고 부른답니다.
Go 표준 라이브러리에서는 sync.Mutex
와 다음 두 메서드를 통해 상호 배제를 제공해요.
Lock
Unlock
Inc
메서드에서 보는 것처럼 Lock
과 Unlock
호출로 둘러싸인 코드 블록은 상호 배제로 실행된답니다.
Value
메서드처럼 defer
를 사용하면 뮤텍스가 잠금 해제되는 것을 보장할 수도 있어요.
package main import ( "fmt" "sync" "time" ) // SafeCounter는 동시에 사용해도 안전해요. type SafeCounter struct { mu sync.Mutex v map[string]int } // Inc는 주어진 키의 카운터를 증가시켜요. func (c *SafeCounter) Inc(key string) { c.mu.Lock() // Lock으로 c.v 맵에는 한 번에 하나의 고루틴만 접근할 수 있어요. c.v[key]++ c.mu.Unlock() } // Value는 주어진 키에 대한 카운터의 현재 값을 반환해요. func (c *SafeCounter) Value(key string) int { c.mu.Lock() // Lock으로 c.v 맵에는 한 번에 하나의 고루틴만 접근할 수 있어요. defer c.mu.Unlock() return c.v[key] } func main() { c := SafeCounter{v: make(map[string]int)} for i := 0; i < 1000; i++ { go c.Inc("somekey") } time.Sleep(time.Second) fmt.Println(c.Value("somekey")) }
go
이 예제에서는 SafeCounter
구조체를 정의했어요. 이 구조체는 sync.Mutex
를 포함하고 있으며, 내부적으로 map[string]int
타입의 v
를 가지고 있죠.
Inc
메서드는 특정 키의 카운터를 증가시키는 역할을 해요. 이때 c.mu.Lock()
으로 먼저 뮤텍스를 잠그고, c.v[key]++
로 맵의 값을 증가시킨 후, c.mu.Unlock()
으로 뮤텍스를 풀어주는 과정을 거치게 되죠. 이렇게 하면 c.v
맵에는 한 번에 하나의 고루틴만 접근할 수 있게 되면서 경쟁 조건을 피할 수 있답니다.
Value
메서드는 특정 키의 현재 카운터 값을 반환해요. 여기서도 마찬가지로 c.mu.Lock()
으로 뮤텍스를 잠그고, defer c.mu.Unlock()
을 사용하여 함수가 종료되기 전에 뮤텍스를 풀어주도록 했죠. 이렇게 하면 c.v
맵에 안전하게 접근할 수 있게 된답니다.
main
함수에서는 SafeCounter
인스턴스를 생성하고, 1000개의 고루틴을 생성하여 동시에 Inc
메서드를 호출하고 있어요. 1초 후에 Value
메서드를 호출하여 결과를 출력하는데, 뮤텍스를 사용했기 때문에 경쟁 조건 없이 안전하게 카운터 값을 증가시키고 읽어올 수 있답니다.
이렇게 Go에서는 sync.Mutex
를 사용하여 간단하면서도 효과적으로 상호 배제를 구현할 수 있어요. 고루틴 간 통신이 필요하지 않고 단순히 공유 자원에 대한 접근을 제어하고 싶을 때 뮤텍스를 활용해 보세요. 경쟁 조건을 예방하고 안전한 동시성 프로그래밍을 할 수 있을 거예요!