Menu
Coddy logo textTech

sync.Mutex と sync.RWMutex

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 63/107。

チャネルはゴルーチンを調整するためのGoの推奨される方法ですが、共有データを直接保護する必要がある場合もあります。syncパッケージはミューテックスを提供します。これは、一度に1つのゴルーチンだけがリソースにアクセスするようにするロックです。

sync.Mutex には2つのメソッド、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

チャレンジ

簡単

商品在庫レベルを追跡する、スレッドセーフな在庫システムを構築しましょう。mutex を使用して同時実行される読み取りと書き込みを安全に処理し、複数の operation が同時に発生しても data の整合性を保証します。

コードは2つのファイルに分けて整理します。

  • inventory.go: スレッドセーフな在庫管理システムを定義します。

    商品ごとの数量を map に格納し、sync.RWMutex を使用して access を保護する Inventory struct を作成します。在庫は次の operations をサポートする必要があります。

    • NewInventory() *Inventory - 初期化済みの map を持つ新しい在庫を作成します
    • AddStock(product string, quantity int) - 商品の在庫に quantity を追加します(data を modifies するため exclusive lock を使用します)
    • GetStock(product string) int - 商品の current 在庫を返します。見つからない場合は 0 を返します(data を読み取るだけなので read lock を使用します)
    • RemoveStock(product string, quantity int) bool - 十分な在庫が exists する場合に、在庫から quantity を削除します。成功した場合は true、insufficient stock の場合は false を返します(exclusive lock を使用します)

    ロックが常に適切に解放されるように、アンロックには defer を使用してください。

  • main.go: operations を読み取り、スレッドセーフな在庫の動作を示します。

    operations の number を読み取り、その後各 operation を処理します。各 operation には type(addget、または remove)、商品名、および add/remove operations の場合は 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(addget、または remove
    • Product name
    • Quantity(add および remove operations のみ)

例えば、次の入力が与えられた場合:

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 呼び出しで同時に読み取りを行えます。一方、AddStockRemoveStock は inventory を modifies する際に 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: 数量を読み取り、在庫を削除し、適切な結果を出力する
			
		}
	}
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Goオンラインコンパイラ