Posted at

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

More than 5 years have passed since last update.

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
}
}

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

結果は

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
}
}

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回だけ行うオブジェクトです。


http

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を待つときに使います。


http

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
}
}

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型

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

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

になります。

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

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


参考