Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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. 複数のゴルーチン(またはスレッド)が同じメモリ位置にアクセスする。
  2. 少なくとも1つのアクセスが書き込み操作である。
  3. アクセスが同期メカニズムによって保護されていない。

データ競合が発生すると、プログラムの実行結果が非決定論的になり、デバッグが非常に困難になります。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/rpcencoding/gobなどのパッケージが提供されたためです。このコミットがold/netchanというパスにあるのは、そのためです。

技術的詳細

このコミットで修正されたデータ競合は、src/pkg/old/netchan/common.goファイルのclientSet構造体のsyncメソッド内で発生していました。

clientSet構造体は、クライアントの集合を管理しており、その中にclientsというハッシュマップ(map[unackedCounter]int64)と、そのハッシュマップを保護するためのミューテックスmusync.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メソッド内で行われています。

  1. cs.mu.Lock()の追加: seq := make(map[unackedCounter]int64)の直後、for client := range cs.clientsループの開始直前にcs.mu.Lock()が追加されました。これにより、cs.clientsハッシュマップのイテレーションが始まる前に、clientSet構造体に関連付けられたミューテックスmuがロックされます。このロックにより、他のゴルーチンが同時にcs.clientsハッシュマップにアクセスして変更を加えることができなくなります。

  2. cs.mu.Unlock()の追加: for client := range cs.clientsループが終了した直後、つまりハッシュマップのイテレーションが完了した後にcs.mu.Unlock()が追加されました。これにより、ミューテックスが解放され、他のゴルーチンがcs.clientsハッシュマップにアクセスできるようになります。

この変更により、cs.clientsハッシュマップの読み取り(イテレーション)がクリティカルセクションとして保護され、並行書き込みによるデータ競合が効果的に防止されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(syncパッケージ、map型に関する記述)
  • 並行プログラミングにおけるデータ競合に関する一般的な知識
  • Go言語のnetchanパッケージの歴史に関する情報(非推奨化と削除)
  • Go言語のIssueトラッカー(#2713に関する情報)
    • 注: Fixes #2713はコミットメッセージに記載されていますが、現在のGoのIssueトラッカーで直接この古いIssueを見つけることは困難でした。これは、GoのIssueトラッカーが時間の経過とともに移行されたり、古いIssueがアーカイブされたりするためです。しかし、コミットメッセージからデータ競合の修正であることが明確に示されています。