Menu
CoddyTech

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

チャレンジ

簡単

商品在庫レベルを追跡する、スレッドセーフな在庫システムを構築しましょう。ミューテックスを使用して同時読み取りと書き込みを安全に処理し、複数の操作が同時に発生した場合でもデータの整合性を確保します。

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

  • 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オンラインコンパイラ