syncパッケージのOnce, WaitGroup, Mutex

## はじめに

Githubで公開されているソースコードを読みながら、Go言語を勉強しています。

Go言語には並列処理の機能があります。

Githubで公開されているソースコードを読むために、Go言語の並列処理について学ぶ必要がありました。

Go言語には同期処理、排他制御などの機能を提供する便利なsyncパッケージがあります。

今回はこの便利なsyncパッケージのOnce, WaitGroup, Mutexについて書きたいと思います。

この記事ではGo言語のインストール方法や構文などの基本的なことについては触れません。もし、それらに興味があるならば、こちらの記事が参考になると思います。

- [Go言語の初心者が見ると幸せになれる場所](http://qiita.com/tenntenn/items/0e33a4959250d1a55045)

また、Go言語の並列処理に関する goroutine, channel, select, close などについてはこちらの記事にわかりやすくまとまっています。

- [Go の並行処理](http://jxck.hatenablog.com/entry/20130414/1365960707)

## sync.Once

まずは、sync.Onceです。関数を一度だけ呼び出したい場合に使用します。

``` once_sample.go
package main

import "fmt"
import "sync"

func main() {
	var once sync.Once
	
	once.Do(func() {fmt.Println("A")})
	once.Do(func() {fmt.Println("B")}) // こちらの関数は呼び出されない
}
```

こちらはGo Playgroundへのリンクです。

- [once_sample](http://play.golang.org/p/vrZ6thRhct)

余談ですが、play goと検索したところ、打ちながら囲碁のルールと基本的なテクニックを学ぶことができるサイトを見つけました。

- [インタラクティブ囲碁入門](http://playgo.to/iwtg/jp/)

## sync.WaitGroup

sync.WaitGroupについてはこちらの記事にわかりやすくまとまっています。

- [Goルーチンで並列化する方法: 6秒かかる処理を3秒にしよう](http://qiita.com/suin/items/82ecb6f63ff4104d4f5d)

自分のために少しまとめておきます。

同期待ちにsync.WaitGroupを使用します。

sync.WaitGroupには、定石があるように思います。

それはgoroutineの起動前にAdd(1)、goroutineの終了前にDone()、同期待ちにWait()という定石です。

``` wait_sample.go
package main

import "fmt"
import "sync"

func main() {
	var wait sync.WaitGroup
	
	wait.Add(1) // goroutineの起動前
	go func() {
		defer wait.Done() // goroutineの終了前
		fmt.Println("A")
	}()
	
	wait.Add(1) // goroutineの起動前
	go func() {
		defer wait.Done() // goroutineの終了前
		fmt.Println("B")
	}()
	
	wait.Wait() // 同期待ち
}
```

こちらはGo Playgroundへのリンクです。

[wait_sample.go](http://play.golang.org/p/e1sdZqFye_)

## sync.Mutex

排他制御にsync.Mutexを使用します。

### case1

まずはsync.Mutexを使用しない場合を見てみます。

``` case1.go
package main

import "fmt"
import "sync"

func main() {
	c := 0
	var w sync.WaitGroup
	
	for i := 0; i < 300; i++ {
		w.Add(1)
		go func() {
			defer w.Done()
			c++
		}()
	
		w.Add(1)
		go func() {
			defer w.Done()
			fmt.Println(c)
		}()
	}
	
	w.Wait()
}
```

期待する結果は１から３００が出力されることです。

期待しない結果になることを確認します。

こちらはGo Playgroundへのリンクです。

[case1.go](http://play.golang.org/p/AM3KaZZjYW)

同じ数字が出力されていること、最後が３００でないことから期待しない結果だとわかります。

### case2

なにが起こったのか確認します。

``` case2.go
package main

import "fmt"
import "sync"

func main() {
	c := 0
	var w sync.WaitGroup
	var m sync.Mutex
	
	for i := 0; i < 300; i++ {
		w.Add(1)
		go func() {
			defer w.Done()
			m.Lock()
			c++
		}()
	
		w.Add(1)
		go func() {
			defer w.Done()
			m.Unlock()
			fmt.Println(c)
		}()
	}
	
	w.Wait()
}
```

こちらはGo Playgroundへのリンクです。

[case2.go](http://play.golang.org/p/HngjIA4DGx)

実行中にパニックが生じます。

```
panic: sync: unlock of unlocked mutex
```

前者のgoroutineでロックして、後者のgoroutineでアンロックしています。

後者のgoroutineが前者のgoroutineを追い越し、アンロックしたミューテックスを、ロックする前に、再度アンロックしたようですね。

### case3

つぎにsync.Mutexを使用する場合を見てみます。

``` case3.go
package main

import "fmt"
import "sync"

func main() {
	c := 0
	var w sync.WaitGroup
	var m sync.Mutex
	
	for i := 0; i < 300; i++ {
		w.Add(1)
		m.Lock()
		go func() {
			defer w.Done()
			defer m.Unlock()
			c++
		}()
	
		w.Add(1)
		m.Lock() // ロック中ならば、アンロックを待つ
		go func() {
			defer w.Done()
			defer m.Unlock()
			fmt.Println(c)
		}()
	}
	
	w.Wait()
}
```

こちらはGo Playgroundへのリンクです。

[case3.go](http://play.golang.org/p/3mtvTFCglI)

期待する結果です。

実装でこちらを参照しました。

- [Go言語　排他処理](http://kobegdg.blogspot.jp/2013/07/go.html)

私の扱った例が悪かったかもしれません。

期待する結果ですが、すでに、並列処理になっていませんね。。。

## まとめ

syncパッケージについてはこちらの記事にわかりやすくまとまっています。

- [Big Sky :: golang の sync パッケージの使い方](http://mattn.kaoriya.net/software/lang/go/20140625223125.htm)

いろいろと試した結果をわかりやすくまとめたつもりです。

sync.Mutexで扱った例は少し適切ではなかったかもしれませんが、参考になれば幸いです。