Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

  1. トップページ
  2. Go辞典
  3. sync.Mutex / RWMutex

sync.Mutex / RWMutex

複数のgoroutineが同じデータに同時アクセスすると競合状態(race condition)が発生します。『sync.Mutex』は1つのgoroutineだけがクリティカルセクションに入れるよう排他制御します。読み取りが多い場合は『sync.RWMutex』が効率的です。

構文
import "sync"

// Mutex(相互排他ロック)
var mu sync.Mutex

mu.Lock()           // ロックを取得します(他がロック中はブロックします)。
defer mu.Unlock()   // ロックを解放します(deferで確実に解放します)。

// RWMutex(読み書きロック)
var rwmu sync.RWMutex

// 書き込み時は排他ロック(読み書き全てをブロック)
rwmu.Lock()
defer rwmu.Unlock()

// 読み取り時は共有ロック(他の読み取りと並行可能、書き込みはブロック)
rwmu.RLock()
defer rwmu.RUnlock()
メソッド一覧
メソッド概要
Lock()ミューテックスをロックします。他のgoroutineがロック中の場合はブロックします。
Unlock()ミューテックスのロックを解放します。ロックした側が解放する必要があります。
TryLock()ロックを試みます(Go 1.18+)。取得できなかった場合はfalseを返します。
RLock()読み取りロックを取得します(RWMutexのみ)。複数の読み取りを並行実行できます。
RUnlock()読み取りロックを解放します(RWMutexのみ)。
サンプルコード
package main

import (
    "fmt"
    "sync"
)

// Mutexで保護されたカウンターです。
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // 確実にアンロックします。
    c.count++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

// RWMutexで保護されたキャッシュです(読み取りが多い場合に効率的)。
type Cache struct {
    rwmu sync.RWMutex
    data map[string]string
}

func (c *Cache) Set(key, value string) {
    c.rwmu.Lock() // 書き込みは排他ロックです。
    defer c.rwmu.Unlock()
    c.data[key] = value
}

func (c *Cache) Get(key string) (string, bool) {
    c.rwmu.RLock() // 読み取りは共有ロックです(複数のgoroutineが同時に実行できます)。
    defer c.rwmu.RUnlock()
    v, ok := c.data[key]
    return v, ok
}

func main() {
    // SafeCounterの並行更新
    counter := &SafeCounter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }
    wg.Wait()
    fmt.Println("カウンター(正しく1000になるはず):", counter.Value())

    // Cacheの並行読み書き
    cache := &Cache{data: make(map[string]string)}
    cache.Set("言語", "Go")
    cache.Set("バージョン", "1.22")

    if v, ok := cache.Get("言語"); ok {
        fmt.Println("キャッシュ取得:", v)
    }
}
概要

競合状態はGoの『go test -race』コマンド(レースディテクター)で検出できます。Mutexを使うとgoroutineのパフォーマンスが低下するため、可能であればchannelを使ったデータの受け渡しによって共有を避けるアプローチが推奨されます。Goの格言「メモリを共有することで通信するな、通信することでメモリを共有せよ」の通りです。

Mutexはコピーしてはいけません。MutexやRWMutexを含む構造体は必ずポインタで渡してください。また、Lock()後にUnlock()を忘れるとデッドロックが発生します。defer mu.Unlock()のパターンで確実に解放することを強く推奨します。

goroutineの基本は『goroutine』、goroutineの完了待ちは『sync.WaitGroup』を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。