sync.Mutex & sync.RWMutex
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 63번째.
채널은 고루틴을 조정하는 Go의 선호되는 방식이지만, 때로는 공유 데이터를 직접 보호해야 할 때가 있습니다. sync 패키지는 뮤텍스(mutexes)를 제공합니다. 이는 한 번에 하나의 고루틴만 리소스에 접근할 수 있도록 보장하는 잠금 장치입니다.
sync.Mutex에는 Lock()과 Unlock()이라는 두 가지 메서드가 있습니다. 고루틴이 Lock()을 호출하면 독점적인 접근 권한을 얻습니다. Lock()을 호출하는 다른 고루틴들은 Unlock()이 호출될 때까지 대기(block)하게 됩니다:
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}defer c.mu.Unlock()을 사용하면 함수가 조기에 반환되거나 패닉(panic)이 발생하더라도 잠금(lock)이 해제되도록 보장합니다. 이는 일반적이고 권장되는 패턴입니다.
읽기 작업은 빈번하지만 쓰기 작업은 드문 경우, sync.RWMutex가 더 나은 성능을 제공합니다. 여러 읽기 작업이 동시에 수행되는 것을 허용하지만, 쓰기 작업은 독점적인 접근 권한을 갖습니다:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // 여러 읽기 작업이 허용됨
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // 쓰기를 위한 배타적 접근
defer c.mu.Unlock()
c.data[key] = value
}읽기 작업에는 RLock()/RUnlock()을 사용하고, 쓰기 작업에는 Lock()/Unlock()을 사용하세요. 이를 통해 동시 읽기를 허용하면서도 쓰기 작업의 안전을 보장할 수 있습니다.
챌린지
쉬움제품 재고 수준을 추적하는 스레드 안전(thread-safe) 인벤토리 시스템을 구축해 보겠습니다. 여러분의 시스템은 뮤텍스(mutex)를 사용하여 동시 읽기 및 쓰기를 안전하게 처리하며, 여러 작업이 동시에 발생할 때 데이터 무결성을 보장합니다.
코드는 두 개의 파일로 구성됩니다:
inventory.go: 스레드 안전 인벤토리 관리 시스템을 정의합니다.맵에 제품 수량을 저장하고
sync.RWMutex를 사용하여 접근을 보호하는Inventory구조체를 생성하세요. 인벤토리는 다음 작업들을 지원해야 합니다:NewInventory() *Inventory- 초기화된 맵을 가진 새 인벤토리를 생성합니다.AddStock(product string, quantity int)- 제품의 재고에 수량을 추가합니다 (데이터를 수정하므로 배타적 잠금(exclusive lock)을 사용하세요).GetStock(product string) int- 제품의 현재 재고를 반환하거나, 찾지 못한 경우 0을 반환합니다 (데이터를 읽기만 하므로 읽기 잠금(read lock)을 사용하세요).RemoveStock(product string, quantity int) bool- 충분한 재고가 있는 경우 재고에서 수량을 제거합니다. 성공하면true를, 재고가 부족하면false를 반환합니다 (배타적 잠금을 사용하세요).
잠금이 항상 제대로 해제되도록
defer를 사용하여 언락(unlock)하는 것을 잊지 마세요.main.go: 작업을 읽고 스레드 안전 인벤토리를 시연합니다.작업의 수를 읽은 다음, 각 작업을 처리합니다. 각 작업은 유형(
add,get, 또는remove), 제품 이름, 그리고add/remove작업의 경우 수량을 가집니다.각 작업에 대해 결과를 출력하세요:
add:Added [quantity] [product]를 출력합니다.get:[product]: [stock] in stock을 출력합니다.remove: 성공하면Removed [quantity] [product]를 출력하고, 그렇지 않으면Insufficient stock for [product]를 출력합니다.
다음 입력이 제공됩니다:
- 줄 1: 작업의 수 (정수)
- 다음 줄들: 각 작업에 대해:
- 작업 유형 (
add,get, 또는remove) - 제품 이름
- 수량 (
add및remove작업에만 해당)
- 작업 유형 (
예를 들어, 다음과 같이 주어지면:
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
apples출력은 다음과 같아야 합니다:
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockRWMutex는 여러 GetStock 호출이 동시에 읽을 수 있도록 허용하는 반면, AddStock과 RemoveStock은 인벤토리를 수정할 때 배타적 접근 권한을 얻습니다.
치트 시트
sync 패키지는 여러 고루틴이 동일한 리소스에 직접 액세스해야 할 때 공유 데이터를 보호하기 위해 뮤텍스(mutexes)를 제공합니다.
sync.Mutex
sync.Mutex는 Lock()과 Unlock()을 사용하여 배타적 액세스를 제공합니다:
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}Unlock()과 함께 defer를 사용하면 함수가 조기에 반환되거나 패닉이 발생하더라도 잠금이 해제되도록 보장합니다.
sync.RWMutex
sync.RWMutex는 여러 명의 동시 읽기 작업자(reader)를 허용하지만 쓰기 작업자(writer)에게는 배타적 액세스를 제공합니다:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // 여러 읽기 작업자 허용
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // 쓰기 작업을 위한 배타적 액세스
defer c.mu.Unlock()
c.data[key] = value
}읽기 작업에는 RLock()/RUnlock()을 사용하고 쓰기 작업에는 Lock()/Unlock()을 사용하세요.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 연산 횟수 읽기
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// 새 인벤토리 생성
inventory := NewInventory()
// 각 연산 처리
for i := 0; i < numOps; i++ {
// 연산 유형 읽기
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
// 제품 이름 읽기
productLine, _ := reader.ReadString('\n')
product := strings.TrimSpace(productLine)
// TODO: 각 연산 유형 처리 (add, get, remove)
// "add" 및 "remove"의 경우, 입력에서 수량을 읽음
// 적절한 인벤토리 메서드 호출
// 챌린지 설명에 따라 결과 출력
switch opType {
case "add":
// TODO: 수량을 읽고, 재고를 추가하고, 결과를 출력함
case "get":
// TODO: 재고를 가져오고, 결과를 출력함
case "remove":
// TODO: 수량을 읽고, 재고를 제거하고, 적절한 결과를 출력함
}
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서9동시성 & OOP
Goroutines 기초Channels & 통신Buffered vs Unbuffered 채널Select 문sync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe 구조체 설계요약 - Worker Pool