Go言語でMutexを使ってみる
並列処理を勉強するのにGoを使い始めた。
全くの初心者なので、手始めにMutexを使ってみた。
今回興味があったこと
ある変数にMutexを使った時/使っていない時で、値の読み出しや書き込みがどんな感じで異なるのか興味を持った。
Mutex自体は、難しいことはわからないが、資源が使用中かどうかのフラグになっている。
https://wa3.i-3-i.info/word13360.html
Mutexを使っていると複数スレッドからのアクセスが同期されるので、結果の整合性が保証される。
逆にMutexを使っていないと、複数スレッドが同時にアクセスしてくる場合があり、結果の不整合が発生する。
この結果の不整合がどの程度起こるものなのか感覚をつかむのに簡単な実験をしてみた。
Mutexのご利益を検証する
初期値が0のcounterという変数を、goルーチンを使ってインクリメントまたはデクリメントする。
この時、Mutexでcounter守っている時と守っていない時で結果が変わるのか調べる。
Mutexありのコード
package main import ( "fmt" "sync" ) func main() { var counter int var lock sync.Mutex var wg sync.WaitGroup decrement := func() { lock.Lock() defer lock.Unlock() counter-- fmt.Printf("Decremented! Counter:%d\n", counter) } increment := func() { lock.Lock() defer lock.Unlock() counter++ fmt.Printf("Incremented! Counter:%d\n", counter) } for i := 0; i <= 5; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } for i := 0; i <= 5; i++ { wg.Add(1) go func() { defer wg.Done() decrement() }() } wg.Wait() fmt.Printf("Calculation Complete! Counter:%d\n", counter) }
Mutexなしのコード
package main import ( "fmt" "sync" ) func main() { var counter int var wg sync.WaitGroup decrement := func() { defer wg.Done() counter-- fmt.Printf("Decremented! Counter:%d\n", counter) } increment := func() { defer wg.Done() counter++ fmt.Printf("Incremented! Counter:%d\n", counter) } for i := 0; i <= 5; i++ { wg.Add(1) go func() { increment() }() } for i := 0; i <= 5; i++ { wg.Add(1) go func() { decrement() }() } wg.Wait() fmt.Printf("Calculation Complete! Counter:%d\n", counter) }
結果
Mutexあり/なしのコードでCounterの最終値をまとめた結果が次の表になる。
試行 | Mutexあり | Mutexなし |
---|---|---|
1 | 0 | 0 |
2 | 0 | 0 |
3 | 0 | 0 |
4 | 0 | 0 |
5 | 0 | 1 |
6 | 0 | 0 |
7 | 0 | 0 |
8 | 0 | 0 |
9 | 0 | -1 |
10 | 0 | 0 |
Mutexがある場合は全て0だが、Mutexがない場合は0以外に1や-1になっていることがある。
ちょっと強引ではあるが、Mutexでアクセスを同期しないと、結果の不整合が起こることが確認できた。
参考
着想は『Go言語による並行処理』の3.2.2から得た。
www.oreilly.co.jp