Go言語のMutexで共有資源へのアクセス制御

Go言語のMutexについて

Go言語のMutex(ミューテックス)は、並行処理において共有資源へのアクセスを制御するための重要な同期手段です。
Goでは、標準ライブラリのsyncパッケージにMutex型が提供されており、これを使ってスレッド間の競合状態を防ぐことができます。

基本的な使い方

sync.Mutexは、排他制御を提供するための基本的なツールです。
Mutexの主要なメソッドには、LockとUnlockがあります。
Lockメソッドは、mutexをロックし、そのリソースへのアクセスを独占します。
Unlockメソッドは、ロックを解除し、他のゴルーチンがリソースにアクセスできるようにします。

package main

import (
  "fmt"
  "sync"
)

var (
  counter int
  mu      sync.Mutex
)

func increment() {
  mu.Lock()
  counter++
  mu.Unlock()
}

func main() {
  var wg sync.WaitGroup

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

  wg.Wait()
  fmt.Println("Counter:", counter)
}

この例では、複数のゴルーチンがcounter変数をインクリメントしていますが、mu.Lockとmu.Unlockで囲むことで、同時にcounterにアクセスできるのは一つのゴルーチンだけになります。
これにより、データ競合や不整合が防がれます。

Mutexの動作

Mutexは、2つの状態(ロックされている、ロックされていない)を持ちます。
Lockメソッドを呼び出すと、Mutexはロックされ、他のゴルーチンが同じMutexをロックしようとすると、そのゴルーチンは待機状態になります。
Unlockメソッドを呼び出すと、Mutexはロックを解除し、待機中のゴルーチンのいずれかがロックを取得します。

ロックのデッドロック

Mutexの使用には注意が必要です。
特に、デッドロックの問題に気をつけなければなりません。
デッドロックは、複数のゴルーチンが互いにリソースを要求しあって無限に待機する状態です。
以下にデッドロックの例を示します。

package main

import (
  "sync"
)

var (
  mu1 sync.Mutex
  mu2 sync.Mutex
)

func deadlock() {
  mu1.Lock()
  mu2.Lock()
  mu2.Unlock()
  mu1.Unlock()
}

func main() {
  go deadlock()
  mu1.Lock()
  mu2.Lock()
  mu2.Unlock()
  mu1.Unlock()
}

このコードでは、deadlock関数とmain関数で異なる順序でMutexをロックしようとしています。
この場合、main関数がmu1をロックし、deadlock関数がmu1をロックしようとすると、デッドロックが発生する可能性があります。
これを避けるためには、ロックの順序を統一することが推奨されます。

ロックの性能

Mutexは非常に効率的ですが、必要以上にロックすることでパフォーマンスが低下する可能性があります。
ロックの範囲をできるだけ狭くし、できるだけ少ない時間だけロックを保持することが重要です。
これにより、他のゴルーチンがより速くロックを取得できるようになり、全体的なパフォーマンスが向上します。

まとめ

Goのsync.Mutexは、並行処理における共有資源へのアクセスを制御するための強力なツールです。
LockとUnlockメソッドを使ってリソースへのアクセスを管理し、デッドロックに注意しながら使用することが重要です。
適切に使うことで、効率的で安全な並行処理を実現できます。