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() を使用します。これにより、書き込みの安全性を確保しながら、読み取りを同時に実行できます。
チャレンジ
簡単商品在庫レベルを追跡する、スレッドセーフな在庫システムを構築しましょう。mutex を使用して同時実行される読み取りと書き込みを安全に処理し、複数の operation が同時に発生しても data の整合性を保証します。
コードは2つのファイルに分けて整理します。
inventory.go: スレッドセーフな在庫管理システムを定義します。商品ごとの数量を map に格納し、
sync.RWMutexを使用して access を保護するInventorystruct を作成します。在庫は次の 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(
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) - Product name
- 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 呼び出しで同時に読み取りを行えます。一方、AddStock と RemoveStock は 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: 数量を読み取り、在庫を削除し、適切な結果を出力する
}
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー9並行処理とオブジェクト指向
Goroutine の基本Channel と通信バッファあり Channel とバッファなし Channelselect 文sync.Mutex と sync.RWMutexsync.WaitGroupスレッドセーフな構造体設計まとめ - Worker Pool自分で練習してみよう: Goオンラインコンパイラ