Goのsyncパッケージで相互排他ロックを行う

[sync](http://golang.org/pkg/sync/)パッケージには、相互排他ロックがあります。
これを使うと意図的に処理にブロックを入れることが出来て
goroutineのチャンネルのやりとりのウェイトのようなことが実装出来ます。

# Mutex型

相互排他ロック。

使わない場合

```go
package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan bool)

	for i := 0; i < 5; i++ {
		go func(i int) {
			time.Sleep(500 * time.Millisecond)
			fmt.Println(i)
			c <- true
		}(i)
	}
	for i := 0; i < 5; i++ {
		<-c
	}
}
```
http://play.golang.org/p/Ehu8JgNFah

結果は

```
0
4
3
2
1
```

```go:
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	m := new(sync.Mutex)
	c := make(chan bool)

	for i := 0; i < 5; i += 1 {
		m.Lock()
		go func(i int) {
			time.Sleep(500 * time.Millisecond)
			fmt.Println(i)
			m.Unlock()

			c <- true
		}(i)
	}
	for i := 0; i < 5; i++ {
		<-c
	}
}

```
http://play.golang.org/p/aYBxt8_8yO

結果

```
0
1
2
3
4
```

すでにLockされている場合は、呼び出したゴルーチンがロック可能になるまでそこでブロックされます。
Unlock()でロック解除。あるゴルーチンでロックして、別のゴルーチンでアンロックをかけても問題ないです。

ちなみにこれならばゴルーチンのみで書けます。
http://play.golang.org/p/8ilFwkIJx5

# RWMutex型

reader/writerの相互排他チェックで使う。

`func (rw *RWMutex) Lock()` で書き込みのロック
`func (rw *RWMutex) Unlock()` で書き込みのアンロック

`func (rw *RWMutex) RLock()` で読み込みのロック
`func (rw *RWMutex) RUnlock()` で読み込みのアンロック


https://github.com/gorilla/context/blob/master/context.go#L14 などで使われています。


# Once型

実行を1回だけ行うオブジェクトです。

```go:http://play.golang.org/p/U2euV7sxWY
package main

import (
     "fmt"
     "sync"
)

func main() {
     var once sync.Once
     onceBody := func() {
          fmt.Println("Only once")
     }
     done := make(chan bool)
     for i := 0; i < 10; i++ {
          go func() {
               once.Do(onceBody) // 1回しか呼ばれない
               done <- true
          }()
     }
     for i := 0; i < 10; i++ {
          <-done
     }
}
```

# WaitGroup型

複数のgoroutineを待つときに使います。

```go:http://play.golang.org/p/l-wX-D3OV-
package main

import (
     "fmt"
     "sync"
     "time"
)

func main() {
     var wg sync.WaitGroup
     var urls = []string{
          "http://www.golang.org/",
          "http://www.google.com/",
          "http://www.somestupidname.com/",
     }

     // なんか重い処理
     heavyExec := func(s string) {
          time.Sleep(1 * time.Second)
          fmt.Println(s)
     }

     for _, url := range urls {
          wg.Add(1)
          go func(url string) {
               defer wg.Done()
               heavyExec(url)
          }(url)
     }
     wg.Wait()
}
```

結果

```
http://www.golang.org/
http://www.somestupidname.com/
http://www.google.com/
```

`func (*WaitGroup) Add()` 内部counterを追加(引数でインクリメントの値を設定可能)
`func (*WaitGroup) Done` counterをデクリメント(1のみデクリメント)
`func (*WaitGroup) Wait` 内部カウンターが0になるまでブロック

# Cond型

先にゴルーチンを用意して、一斉に実行したい場合はCondを使います。

```go
package main

import (
  "fmt"
  "sync"
)

func main() {
  m := new(sync.Mutex)
  c := make(chan bool)
  cond := sync.NewCond(m)

  for i := 0; i < 5; i++ {
    go func(i int) {
      m.Lock()
      defer m.Unlock()
      cond.Wait()
      fmt.Println(i)
      c <- true
    }(i)
  }
  for i := 0; i < 5; i++ {
    go func() {
      cond.Signal()
    }()
    <-c
  }
}

```
http://play.golang.org/p/GO_gqR_YIm

`func (c *Cond) Signal()` で1回ずつ実行。
`func (c *Cond) Broadcast()` で登録されているすべてを実行。

Broadcastを使った場合: http://play.golang.org/p/EC6_Gkhqo5

# Pool型

非同期でくる割り込みに対応したい場合に利用します。

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	c := make(chan bool)

	pool := sync.Pool{
		New: func() interface{} {
			return "New"
		},
	}

	for i := 0; i < 5; i++ {
		go func() {
			time.Sleep(100 * time.Millisecond)
			fmt.Println(pool.Get())
			c <- true
		}()
		<-c
	}
}
```
http://play.golang.org/p/xgkZs_GOug

この場合結果はすべて

```
New
New
New
New
New
```
になります。

途中で別なものを取得したい場合

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	c := make(chan bool)

	pool := sync.Pool{
		New: func() interface{} {
			return "New"
		},
	}

	// 175ms後と350ms後に割り込みを入れる　
	for i := 0; i < 2; i++ {
		go func(i int) {
			time.Sleep(175 * time.Duration(i+1) * time.Millisecond)
			pool.Put("Put")
		}(i)
	}

	for i := 0; i < 5; i++ {
		go func() {
			time.Sleep(100 * time.Millisecond)
			fmt.Println(pool.Get())
			c <- true
		}()
		<-c
	}
}
```

http://play.golang.org/p/QbGwQtn1T6

結果

```
New
Put
New
Put
New
```

# 参考

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