Goでは、競合するゴルーチンの同期のためにsyncパッケージの構造体が使用されます。最も一般的なものはsync.Mutex、sync.RWMutex、sync.WaitGroupです。
**sync.Mutex**は、共有データへのアクセス時に相互排他メカニズムを提供します。そのメソッドはLock()(ロック)とUnlock()(アンロック)です。
**sync.RWMutex**は、通常のミューテックスの機能を拡張し、並行して読み取りを許可し、排他での書き込みを許可します。
**sync.WaitGroup**は、ゴルーチンのグループが完了するのを待つためのものです。Add(int)、Done()、Wait()を使用して、並列処理のライフサイクルを制御します。
例えば:
var mu sync.RWMutex data := 0 // 読み込み mu.RLock() fmt.Println(data) mu.RUnlock() // 書き込み mu.Lock() data = 42 mu.Unlock()
注意点:
Unlockをdeferブロック内で使用し、パニック時でもミューテックスを忘れずにアンロックしてください。WaitGroup.Done()の呼び出し回数がAdd()に一致することを確認してください。同じゴルーチン内で同じsync.Mutex(またはRWMutex)を2回連続して取得することはできますか?何が起こりますか?
回答:いいえ、一度もUnlock()を行わずに同じMutexでLock()を2回連続して呼び出すと、ゴルーチンは永遠にブロックされます(デッドロック)。Goではミューテックスは再帰的ではありません。
例:
var mu sync.Mutex mu.Lock() // ... mu.Lock() // デッドロック:同じスレッドがすでにロックを保持しているため、永遠にブロックされる
エピソード
ハイロードREST APIプロジェクトで、開発者はリクエスト処理全体を1つのミューテックスでラップしました。これにより、パフォーマンスが急激に低下し、同時に処理できるリクエストは1つだけで、数千のクライアントをサポートする予定でした。理由は、MutexとRWMutexの違いを知らず、並行した読み取りを無視したからです。
エピソード
構造体をMutex内でコピーしていたチームのメンバーの1人が、誤って他の関数にコピーを渡しました。これにより、"sync: copy of sync.Mutex"というパニックメッセージが表示され、高負荷時に生産環境でクラッシュしました。
エピソード
WaitGroupを使用する際に、いくつかのゴルーチンでDone()を呼び忘れたため、Wait()が永遠に待機してメインスレッドをブロックしました。その結果、サービスは手動再起動まで利用できなくなりました。