Mutexとは?
「Mutex(ミューテックス)」は「相互排他」を表す"mutual exclusion"の略で、クリティカルセクション(共有リソースに対する排他的なアクセスが必要な場所)を保護する方法。
syncパッケージで定義されている。
Mutexを使うことで、並行処理を実装する際に、安全に共有リソースに対する排他的アクセスを実現できる。
これだけだと良くわからないのでサンプルコードを書いてみます。
Mutexのサンプルコード
2つのゴルーチンから、共通の変数をインクリメント・デクリメントする処理を書いてみる。
Mutexを使うことで共通変数count
を「インクリメントするときには、デクリメントできない」といった相互排他制御が簡単に実装できる。
package main import ( "fmt" "sync" "time" ) func main() { var count int var mu sync.Mutex increment := func() { mu.Lock() // muをロックする defer mu.Unlock() // 処理完了したらmuのロック解除 count++ time.Sleep(1 * time.Second) fmt.Printf("Increment. count: %d\n", count) } decrement := func() { mu.Lock() defer mu.Unlock() count-- time.Sleep(1 * time.Second) fmt.Printf("Decrement. count: %d\n", count) } var wg sync.WaitGroup // インクリメントするゴルーチンを5つ生成 for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } // デクリメントするゴルーチンを5つ生成 for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() decrement() }() } wg.Wait() fmt.Println("DONE!!") }
Go Playground: https://play.golang.org/p/thnVw7_GShw
実行結果は次の通り。
インクリメントかデクリメントどちらかしか実行されていないことがわかる。
$ go run main.go Increment. count: 1 Decrement. count: 0 Decrement. count: -1 Decrement. count: -2 Decrement. count: -3 Increment. count: -2 Increment. count: -1 Increment. count: 0 Decrement. count: -1 Increment. count: 0 DONE!!
ポイント
Unlockの呼び出しにはdeferを使うとよい!!
こうすることで、panicになったとしても確実に呼び出される。
呼び出し失敗したり、忘れたりするとデッドロックが発生してしまう。
RWMutexとは?
RWMutexは概念的にはMutexと同じもので、メモリへのアクセスを保護してくれる。
同じくsyncパッケージで定義されている。
Mutexとの違いは、RWMutexでは2種類のロックが提供されている点。
RLock
: 読み取り用のロック。RLock同士はブロックせず、Lockのみがブロックされる。解除時はRUnlock
を使うLock
: Mutexと同じロック。RLock, Lock双方をブロックする。
RWMutexのサンプルコード
package main import ( "fmt" "sync" "time" ) func main() { var count int var mu sync.RWMutex increment := func() { mu.Lock() // muをロックする defer mu.Unlock() // 処理完了したらmuのロック解除 count++ time.Sleep(1 * time.Second) fmt.Printf("Increment. count: %d\n", count) } decrement := func() { mu.Lock() defer mu.Unlock() count-- time.Sleep(1 * time.Second) fmt.Printf("Decrement. count: %d\n", count) } // Readロック read := func() { mu.RLock() defer mu.RUnlock() time.Sleep(1 * time.Second) fmt.Printf("reading. count: %d\n", count) } var wg sync.WaitGroup // インクリメントするゴルーチンを5つ生成 for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } // デクリメントするゴルーチンを5つ生成 for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() decrement() }() } // 読み取りするゴルーチンを5つ生成 for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() read() }() } wg.Wait() fmt.Println("DONE!!") }
Go Playground: https://play.golang.org/p/OiC48KkJmwk
実行結果は次の通り。
RLock
同士はブロックしないので、readがほぼ同時に実行されています。
reading. count: 0 Increment. count: 1 reading. count: 1 reading. count: 1 reading. count: 1 reading. count: 1 Increment. count: 2 Increment. count: 3 Decrement. count: 2 Increment. count: 3 Increment. count: 4 Decrement. count: 3 Decrement. count: 2 Decrement. count: 1 Decrement. count: 0 DONE!!
参考図書
Tour of Goを終えた人にオススメです!!