goroutineによる並行処理で共有リソースにアクセスしなければならないとき、排他制御が必要になります。ここでは、sync.Mutexを利用した排他制御の実装方法を確認します。(mutexは、mutual exclusionの略です。)
目次
排他制御をせずにエラーが発生する例
まずは、排他制御をしない場合に発生するエラーについて確認します。
package main
import (
"fmt"
"sync"
)
type counter struct {
v map[string]int
}
func (c *counter) Inc(wg *sync.WaitGroup) {
defer wg.Done()
c.v["wakuwaku"]++
}
func main() {
wg := sync.WaitGroup{}
c := counter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
wg.Add(1)
go c.Inc(&wg)
}
wg.Wait()
fmt.Printf("%v", c.v)
}
上記処理を実行すると、以下エラーが発生します。
fatal error: concurrent map writes
map型の変数に対して、書き込みが競合したことによるエラーです。
goコマンドの -raceオプション
を利用して競合を検出することもできます。
$ go run -race main.go
==================
WARNING: DATA RACE
(省略)
==================
==================
WARNING: DATA RACE
(省略)
==================
==================
WARNING: DATA RACE
(省略)
==================
map[wakuwaku:1000]Found 3 data race(s)
exit status 66
競合が存在する場合、上記のようにWARNINGが表示されます。
sync.Mutexによる排他制御
sync.Mutexを利用して、書き込みが競合しないように排他制御します。
package main
import (
"fmt"
"sync"
)
type counter struct {
mu sync.Mutex // 追加
v map[string]int
}
func (c *counter) Inc(wg *sync.WaitGroup) {
defer wg.Done()
c.mu.Lock() // 追加
c.v["wakuwaku"]++
c.mu.Unlock() // 追加
}
func main() {
wg := sync.WaitGroup{}
c := counter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
wg.Add(1)
go c.Inc(&wg)
}
wg.Wait()
fmt.Printf("%v", c.v)
}
map[wakuwaku:1000]
-raceオプション
での確認でもWARNINGが表示されなくなりました。
$ go run -race main.go
map[wakuwaku:1000]