LaTeX962830 views
中学社会669043 views
雑学1473736 views
小学社会310694 views
Computer368520 views
高校化学2926093 views
高校国語788661 views
MathPython498173 views
高校倫理1441073 views
りんご212052 views
Help
Tools

English

Mutex で共有データを安全に扱う

複数の goroutine から同じ変数を読み書きすると、データ競合(data race)が起きることがあります。これを防ぐのが sync.Mutex です。

データ競合とは

次のコードには問題があります。

package main

import (
    "fmt"
    "sync"
)

func main() {
    counter := 0
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter++
        }()
    }

    wg.Wait()
    fmt.Println(counter) // 1000 にならないことがある
}

counter++ は「読み取り → 加算 → 書き込み」の 3 ステップから成ります。複数の goroutine がこれを同時に行うと、お互いの変更を上書きしてしまいます。

Mutex で排他制御する

Mutex は「ロック」を提供します。ロックを取得した goroutine だけが処理を進められ、他は待機します。

package main

import (
    "fmt"
    "sync"
)

func main() {
    counter := 0
    var mu sync.Mutex
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            counter++
            mu.Unlock()
        }()
    }

    wg.Wait()
    fmt.Println(counter) // 必ず 1000 になる
}

Lock() でロックを取得し、Unlock() で解放します。Lock と Unlock の間は、一度に 1 つの goroutine しか実行できません。

defer で Unlock する

Unlock を忘れるとデッドロックになります。defer を使うと安全です。

mu.Lock()
defer mu.Unlock()
// 処理

途中で return や panic が起きても、defer なら確実に Unlock されます。

構造体に埋め込む

Mutex を構造体に埋め込むパターンはよく使われます。

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

データとロックをセットにすることで、安全なアクセスをカプセル化できます。

RWMutex で読み取りを並行化

sync.RWMutex は読み取りと書き込みを区別します。

Mutex

読み取りも書き込みも、一度に 1 つの goroutine しかアクセスできない。

RWMutex

書き込みは 1 つだけ。読み取りは複数同時に可能。

読み取りが多く書き込みが少ない場合、RWMutex の方が効率的です。

var rw sync.RWMutex

// 読み取り
rw.RLock()
defer rw.RUnlock()
// 読み取り処理

// 書き込み
rw.Lock()
defer rw.Unlock()
// 書き込み処理

読み取りには RLock() / RUnlock()、書き込みには Lock() / Unlock() を使います。

Mutex と channel の使い分け

Go のことわざに「共有メモリで通信するな、通信でメモリを共有せよ」というものがあります。channel でデータを渡す方が Go らしいスタイルとされています。

ただし、単純なカウンタやキャッシュなど、共有状態を保護するだけなら Mutex の方がシンプルなこともあります。状況に応じて使い分けてください。