Golangの箴言 > メモリを共有することで通信するな。通信することでメモリを共有せよ
結論から言うと、この格言は**「sync.Mutex を使った共有状態のロックを避け、チャネル (chan) を使ったデータの受け渡し(所有権の移動)を使え」**ということを説いています。
具体的にどういう時にどうすべきか、2つの対比で解説します。
1. 何を使わず、何を避けるべきか
- 避けるべき手法: グローバル変数や共有ポインタに対し、
sync.Mutex(ロック) を使って安全性を担保すること。 - どういう時に避けるべきか:
- 複数のゴルーチンが、1つの大きなデータ構造(スライスやマップなど)に同時に書き込みや読み込みを行おうとする時。
- 処理の順番が複雑に絡み合うデータパイプラインを作る時。
- なぜ避けるべきか: どこでロックをかけ、どこで解除するかの管理がプログラマの自己責任になり、デッドロック(お互いがロック解除を待ってフリーズする現象)や、ロックの粒度が大きすぎることによるパフォーマンス低下を引き起こしやすいからです。
2. 代わりに何を使うべきか
- 使うべき手法: チャネル (
chan) を使って、データを別のゴルーチンへ送信(メッセージパッシング)すること。 - どういう時に使うべきか:
- あるゴルーチンでの処理結果を、別のゴルーチンに渡して続きの処理をさせたい時。
- 複数のワーカー(ゴルーチン)にタスクを分配し、その結果を一箇所で集計したい時。
- なぜ使うべきか: データをチャネルに「送信」した時点で、そのデータの**「所有権」が次のゴルーチンへ移動した**とみなすことができるからです。ある瞬間においてそのデータを触るゴルーチンは常に1つだけになるため、物理的にデータの競合が発生しません。
具体的なコードの比較(結果の集計)
例えば、「複数のゴルーチンで計算を行い、その結果を1つのリスト(スライス)にまとめる」というケースで比較してみます。
❌ 避けるべきアプローチ(メモリの共有による通信)
共有のスライスをMutex(lock)で守りながら、みんなでよってたかって書き込みます。
⭕ 推奨されるアプローチ(通信によるメモリの共有)
書き込み役を「1人」に限定し、他のゴルーチンはチャネルを通して「これ追加しておいて!」とデータを投げます。
現実的な補足(必ずしもMutexが悪ではない)
Goの公式も「絶対にMutexを使うな」とは言っていません。
たとえば、**「構造体の中に持たせた単純なカウンター」や「オンメモリのシンプルなキャッシュ(map)」**を守るだけであれば、チャネルを大げさに用意するより sync.Mutex を使ったほうがシンプルで高速な場合が多いです。
- データの「流れ(フロー)」を制御したい時 👉 チャネルを使う
- 単なる「状態」を保護したい時 👉 Mutexを使う
このような使い分けが、Go言語における現実的なベストプラクティスになります。