[インデックス 11201] ファイルの概要
このコミットは、Go言語のold/netchan
パッケージ内のcommon.go
ファイルに対する変更です。具体的には、クライアントのハッシュマップにおけるデータ競合(data race)を修正することを目的としています。
コミット
commit 8e99016c80ee8c113674654123a02f0bd9e32ebb
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Tue Jan 17 11:48:20 2012 +0400
old/netchan: fix data race on client hashmap
Fixes #2713.
R=golang-dev, r
CC=golang-dev, mpimenov
https://golang.org/cl/5545065
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8e99016c80ee8c113674654123a02f0bd9e32ebb
元コミット内容
old/netchan: fix data race on client hashmap
Fixes #2713.
変更の背景
このコミットは、Go言語のold/netchan
パッケージにおいて発生していたデータ競合の問題を解決するために行われました。Fixes #2713
という記述から、この問題がGoのIssueトラッカーで報告されていたバグであることがわかります。
データ競合は、複数のゴルーチン(Goの軽量スレッド)が同時に共有データにアクセスし、少なくとも1つのアクセスが書き込みである場合に発生する競合状態の一種です。このような状況では、操作の順序が保証されず、プログラムの動作が予測不能になったり、クラッシュしたりする可能性があります。
この特定のケースでは、clientSet
構造体内のハッシュマップ(cs.clients
)が、複数のゴルーチンから同時に読み書きされる可能性があり、その際に適切な同期メカニズムが欠如していたため、データ競合が発生していました。特に、sync
メソッド内でハッシュマップをイテレートしている最中に、別のゴルーチンがハッシュマップを変更しようとすると問題が生じます。
前提知識の解説
データ競合 (Data Race)
データ競合は、並行プログラミングにおける一般的なバグの一種です。以下の3つの条件がすべて満たされたときに発生します。
- 複数のゴルーチン(またはスレッド)が同じメモリ位置にアクセスする。
- 少なくとも1つのアクセスが書き込み操作である。
- アクセスが同期メカニズムによって保護されていない。
データ競合が発生すると、プログラムの実行結果が非決定論的になり、デバッグが非常に困難になります。Go言語では、go run -race
コマンドを使用することで、実行時にデータ競合を検出する「レース検出器」を有効にすることができます。
ハッシュマップ (Hashmap)
ハッシュマップ(Goではmap
型)は、キーと値のペアを格納するためのデータ構造です。キーを使って高速に値にアクセスできます。Goのマップは、並行アクセスに対して安全ではありません。つまり、複数のゴルーチンが同時にマップを読み書きしようとすると、データ競合が発生する可能性があります。Goのドキュメントでは、マップへの並行アクセスはミューテックスなどの同期プリミティブで保護する必要があると明記されています。
ミューテックス (Mutex)
ミューテックス(Mutual Exclusionの略)は、並行プログラミングにおいて共有リソースへのアクセスを制御するための同期プリミティブです。ミューテックスは、一度に1つのゴルーチンだけが特定のコードセクション(クリティカルセクション)を実行できるようにすることで、データ競合を防ぎます。
Go言語では、sync
パッケージにsync.Mutex
が提供されています。
mu.Lock()
: ミューテックスをロックします。既にロックされている場合、現在のゴルーチンはロックが解放されるまでブロックされます。mu.Unlock()
: ミューテックスをアンロックします。
netchan
パッケージ
netchan
は、Go言語の初期バージョンに存在したパッケージで、ネットワーク越しにチャネル(Goの並行処理プリミティブ)を介して値を送受信するための実験的なメカニズムを提供していました。しかし、このパッケージは後に非推奨となり、Go 1.0のリリース後には標準ライブラリから削除されました。これは、より柔軟で強力なnet/rpc
やencoding/gob
などのパッケージが提供されたためです。このコミットがold/netchan
というパスにあるのは、そのためです。
技術的詳細
このコミットで修正されたデータ競合は、src/pkg/old/netchan/common.go
ファイルのclientSet
構造体のsync
メソッド内で発生していました。
clientSet
構造体は、クライアントの集合を管理しており、その中にclients
というハッシュマップ(map[unackedCounter]int64
)と、そのハッシュマップを保護するためのミューテックスmu
(sync.Mutex
)が含まれています。
問題のコードは、sync
メソッド内でcs.clients
ハッシュマップをイテレートしている部分でした。
// seq remembers the clients and their seqNum at point of entry.
seq := make(map[unackedCounter]int64)
for client := range cs.clients { // ここでデータ競合が発生する可能性があった
seq[client] = client.seq()
}
このfor client := range cs.clients
ループは、cs.clients
ハッシュマップのキーを読み取っています。しかし、この読み取り操作中に、別のゴルーチンがcs.clients
ハッシュマップに対して書き込み操作(要素の追加、削除、変更など)を行うと、ハッシュマップの内部構造が変更され、イテレータが不正な状態になり、データ競合が発生します。Goのマップは並行アクセスに対して安全ではないため、このような状況ではパニックを引き起こす可能性があります。
この問題を解決するために、ハッシュマップのイテレーションを行う前にcs.mu.Lock()
を呼び出してミューテックスをロックし、イテレーションが完了した後にcs.mu.Unlock()
を呼び出してミューテックスをアンロックする変更が加えられました。これにより、ハッシュマップのイテレーション中は他のゴルーチンがハッシュマップにアクセスできなくなり、データ競合が防止されます。
コアとなるコードの変更箇所
--- a/src/pkg/old/netchan/common.go
+++ b/src/pkg/old/netchan/common.go
@@ -165,9 +165,11 @@ func (cs *clientSet) sync(timeout time.Duration) error {
deadline := time.Now().Add(timeout)
// seq remembers the clients and their seqNum at point of entry.
seq := make(map[unackedCounter]int64)
+\tcs.mu.Lock()
for client := range cs.clients {
seq[client] = client.seq()
}
+\tcs.mu.Unlock()
for {
// pending := false
cs.mu.Lock()
コアとなるコードの解説
変更はsrc/pkg/old/netchan/common.go
ファイルのclientSet
構造体のsync
メソッド内で行われています。
-
cs.mu.Lock()
の追加:seq := make(map[unackedCounter]int64)
の直後、for client := range cs.clients
ループの開始直前にcs.mu.Lock()
が追加されました。これにより、cs.clients
ハッシュマップのイテレーションが始まる前に、clientSet
構造体に関連付けられたミューテックスmu
がロックされます。このロックにより、他のゴルーチンが同時にcs.clients
ハッシュマップにアクセスして変更を加えることができなくなります。 -
cs.mu.Unlock()
の追加:for client := range cs.clients
ループが終了した直後、つまりハッシュマップのイテレーションが完了した後にcs.mu.Unlock()
が追加されました。これにより、ミューテックスが解放され、他のゴルーチンがcs.clients
ハッシュマップにアクセスできるようになります。
この変更により、cs.clients
ハッシュマップの読み取り(イテレーション)がクリティカルセクションとして保護され、並行書き込みによるデータ競合が効果的に防止されます。
関連リンク
- Go言語のコミット: https://github.com/golang/go/commit/8e99016c80ee8c113674654123a02f0bd9e32ebb
- Gerrit Code Review (Goのコードレビューシステム): https://golang.org/cl/5545065
参考にした情報源リンク
- Go言語の公式ドキュメント(
sync
パッケージ、map
型に関する記述) - 並行プログラミングにおけるデータ競合に関する一般的な知識
- Go言語の
netchan
パッケージの歴史に関する情報(非推奨化と削除) - Go言語のIssueトラッカー(#2713に関する情報)
- 注:
Fixes #2713
はコミットメッセージに記載されていますが、現在のGoのIssueトラッカーで直接この古いIssueを見つけることは困難でした。これは、GoのIssueトラッカーが時間の経過とともに移行されたり、古いIssueがアーカイブされたりするためです。しかし、コミットメッセージからデータ競合の修正であることが明確に示されています。
- 注: