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.go: 스레드 안전한 재고 관리 시스템을 정의합니다.제품 수량을 맵에 저장하고
sync.RWMutex를 사용하여 접근을 보호하는Inventory구조체를 만드세요. 인벤토리는 다음 작업을 지원해야 합니다:NewInventory() *Inventory- 초기화된 맵을 사용하여 새로운 인벤토리를 생성합니다AddStock(product string, quantity int)- 제품의 재고에 수량을 추가합니다 (데이터를 수정하므로 배타적 잠금을 사용합니다)GetStock(product string) int- 제품의 현재 재고를 반환하거나, 제품을 찾지 못한 경우 0을 반환합니다 (데이터를 읽기만 하므로 읽기 잠금을 사용합니다)RemoveStock(product string, quantity int) bool- 재고가 충분한 경우 재고에서 수량을 제거합니다. 성공하면true를, 재고가 부족하면false를 반환합니다 (배타적 잠금을 사용합니다)
잠금이 항상 올바르게 해제되도록 잠금 해제에
defer를 사용하는 것을 잊지 마세요.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이 배타적 접근 권한을 얻습니다.
직접 해보기
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"의 경우, 입력에서 수량을 읽기
// 적절한 inventory 메서드 호출
// 챌린지 설명에 따라 결과 출력
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 컴파일러