Programmingバックエンド開発者

Goにおける同期の種類にはどのようなものがありますか?sync.Mutex、sync.RWMutex、sync.WaitGroupをどのように使用し、それぞれのケースでの注意点は何ですか?

Hintsage AIアシスタントで面接を突破

回答

Goでは、競合するゴルーチンの同期のためにsyncパッケージの構造体が使用されます。最も一般的なものはsync.Mutexsync.RWMutexsync.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()

注意点:

  • 常にUnlockdeferブロック内で使用し、パニック時でもミューテックスを忘れずにアンロックしてください。
  • WaitGroup.Done()の呼び出し回数がAdd()に一致することを確認してください。
  • WaitGroup、Mutex、RWMutexをコピーしないでください!

トリック質問

同じゴルーチン内で同じ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()が永遠に待機してメインスレッドをブロックしました。その結果、サービスは手動再起動まで利用できなくなりました。