sync.Mutex【Go】
channel以外の方法で異なるgoroutineがアクセスしても、
競合によるエラーが起きないようにするための機能です。
channelではなく、1つのmapに対して異なる2つのgoroutineがアクセスする
次のコードは、エラーが発生することがあります。
func main() { c := make(map[string]int) go func() { for i := 0; i < 10; i++ { c["key"]++ // 2つのgoroutineで同じmapの同じkeyの値にアクセス } }() go func() { for i := 0; i < 10; i++ { c["key"]++ // 2つのgoroutineで同じmapの同じkeyの値にアクセス } }() time.Sleep(1 * time.Second) // 簡易テストのためタイマーを利用 fmt.Println(c, c["key"]) }
2つのgoroutineで、同じmapの同じkeyの値にアクセスしているため、
タイミングによってエラーが発生します。
この問題を解決するのがsync.Mutexです。
今回は、structの1要素としてsync.Mutex型を定義し、
それを使って、競合が起きうる処理の前後でロック・アンロックします。
なお、type structのメソッドでなくても、変数にsync.Mutexを定義し、関数の引数とすることで、
ロック、アンロックを使用できます。
type Counter struct { v map[string]int mux sync.Mutex // sync.Mutexを定義 } func (c *Counter) Write(key string) { // typeに対するメソッド(sync.Mutexはポインタ渡しにすべきなので、ポインタレシーバー) c.mux.Lock() // 処理前にロック defer c.mux.Unlock() // 処理後にはアンロック c.v[key]++ } func (c *Counter) Read(key string) int { // typeに対するメソッド(sync.Mutexはポインタ渡しにすべきなので、ポインタレシーバー) c.mux.Lock() // 処理前にロック defer c.mux.Unlock() // 処理後にはアンロック return c.v[key] } func main() { c := Counter{v: make(map[string]int)} // Counter型として、vにmakeしたmapを定義 go func() { for i := 0; i < 10; i++ { c.Write("key") // 処理ごとにロック・アンロックするため、競合エラーが発生しない } }() go func() { for i := 0; i < 10; i++ { c.Write("key") // 処理ごとにロック・アンロックするため、競合エラーが発生しない } }() time.Sleep(1 * time.Second) fmt.Println(c.v, c.Read("key")) // c.muxを出力すると、値コピーの警告が発生する。&c.muxとすれば解消する }
1つ注意点です。
sync.Mutexは、値渡しにすると、各goroutine内でコピーが作成されて、
コピーに対してロック・アンロックが処理されてしまいます。
これは修正前と同じ、競合エラーを引き起こす可能性を残してしまうということです。
そのため、上記コードでは、typeで定義したstructに対してメソッドを使っているので、
ポインタレシーバーである必要があります。
type定義したメソッドでない、関数の場合は、引数にsync.Mutexを渡すようにします。