syncパッケージには、相互排他ロックがあります。
これを使うと意図的に処理にブロックを入れることが出来て
goroutineのチャンネルのやりとりのウェイトのようなことが実装出来ます。
Mutex型
相互排他ロック。
使わない場合
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
}
}
結果は
0
4
3
2
1
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
}
}
結果
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回だけ行うオブジェクトです。
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を待つときに使います。
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を使います。
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
}
}
func (c *Cond) Signal()
で1回ずつ実行。
func (c *Cond) Broadcast()
で登録されているすべてを実行。
Broadcastを使った場合: http://play.golang.org/p/EC6_Gkhqo5
Pool型
非同期でくる割り込みに対応したい場合に利用します。
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
}
}
この場合結果はすべて
New
New
New
New
New
になります。
途中で別なものを取得したい場合
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
}
}
結果
New
Put
New
Put
New