たそらぼ

日頃思ったこととかメモとか。

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