Menu
CoddyTech

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()을 사용하세요. 이렇게 하면 쓰기의 안전성을 보장하면서 동시에 읽을 수 있습니다.

challenge icon

챌린지

쉬움

제품 재고 수준을 추적하는 스레드 안전 인벤토리 시스템을 만들어 보겠습니다. 뮤텍스를 사용하여 여러 작업이 동시에 발생할 때 데이터 무결성을 보장하면서 동시 읽기 및 쓰기를 안전하게 처리합니다.

코드를 두 개의 파일로 구성합니다:

  • 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 stock

RWMutex를 사용하면 여러 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: 수량 읽기, 재고 제거, 적절한 결과 출력
			
		}
	}
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 Go 컴파일러