sync.Mutex & sync.RWMutex
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 63번째.
채널은 Go에서 고루틴을 조정하는 데 선호되는 방법이지만, 때로는 공유 데이터 자체를 직접 보호해야 합니다. sync 패키지는 한 번에 하나의 고루틴만 리소스에 액세스하도록 보장하는 잠금인 뮤텍스를 제공합니다.
sync.Mutex에는 두 가지 메서드가 있습니다: Lock()과 Unlock()입니다. 고루틴이 Lock()을 호출하면 배타적 액세스 권한을 얻습니다. 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
}defer c.mu.Unlock()를 사용하면 함수가 일찍 반환되거나 패닉이 발생하더라도 잠금이 해제됩니다. 이는 일반적이고 권장되는 패턴입니다.
읽기가 빈번하지만 쓰기가 드문 경우 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()을 사용하세요. 이렇게 하면 동시에 읽을 수 있으며 쓰기의 안전성이 보장됩니다.
챌린지
쉬움제품 재고 수준을 추적하는 스레드 안전 inventory system을 만들어 봅시다. mutexes를 사용하여 동시 읽기와 쓰기를 안전하게 처리하고, 여러 operations가 동시에 발생할 때 data integrity를 보장합니다.
코드를 두 파일에 구성합니다:
inventory.go: 스레드 안전 inventory management system을 정의합니다.제품 수량을 map에 저장하고
sync.RWMutex를 사용하여 access를 보호하는Inventorystruct를 만듭니다. inventory는 다음 operations를 지원해야 합니다:NewInventory() *Inventory- 초기화된 map으로 새로운 inventory를 createsAddStock(product string, quantity int)- 제품의 stock에 quantity를 adds (이것은 data를 modifies하므로 exclusive lock을 사용)GetStock(product string) int- 제품의 current stock을 반환하거나, found되지 않으면 0을 반환 (data를 only 읽으므로 read lock을 사용)RemoveStock(product string, quantity int) bool- sufficient stock이 exists하면 stock에서 quantity를 제거합니다. 성공하면true, insufficient stock이면false를 반환 (exclusive lock을 사용)
lock이 항상 제대로 해제되도록 unlocking에
defer를 사용하는 것을 기억하세요.main.go: operations를 읽고 스레드 안전 inventory를 시연합니다.operations의 number를 읽은 다음 각 operation을 처리합니다. 각 operation에는 type(
add,get또는remove), 제품 이름, 그리고add/removeoperations의 경우 quantity가 포함됩니다.각 operation에 대해 결과를 출력합니다:
add:Added [quantity] [product]를 출력get:[product]: [stock] in stock을 출력remove: 성공하면Removed [quantity] [product]를 출력하고, 그렇지 않으면Insufficient stock for [product]를 출력
다음 입력이 제공됩니다:
- Line 1: operations의 number (integer)
- Following lines: 각 operation에 대해:
- Operation type (
add,get또는remove) - 제품 이름
- Quantity (
add및removeoperations에만 해당)
- Operation type (
예를 들어 다음이 주어지면:
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 calls가 동시에 읽을 수 있도록 하며, AddStock과 RemoveStock은 inventory를 modifying할 때 exclusive access를 얻습니다.
직접 해보기
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직접 연습해 보세요: 온라인 Go 컴파일러