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

[インデックス 14092] ファイルの概要

このコミットは、Go言語の標準ライブラリである encoding/gob パッケージにおけるデータ競合(data race)の修正を目的としています。具体的には、Register 関数および関連する型登録メカニズムにおいて、並行アクセス時に発生しうる競合状態を解消するために、読み書きロック(sync.RWMutex)が導入されました。

コミット

commit e855fcc3073ccb4dc3fa7a3b7dc2b076e5bb54cf
Author: Rob Pike <r@golang.org>
Date:   Tue Oct 9 11:56:38 2012 +1100

    encoding/gob: fix data race in Register
    Fixes #4214.
    
    R=golang-dev, dsymonds, bradfitz
    CC=golang-dev
    https://golang.org/cl/6637047

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e855fcc3073ccb4dc3fa7a3b7dc2b076e5bb54cf

元コミット内容

encoding/gob: fix data race in Register Fixes #4214.

R=golang-dev, dsymonds, bradfitz CC=golang-dev https://golang.org/cl/6637047

変更の背景

encoding/gob パッケージは、Goのデータ構造をシリアライズおよびデシリアライズするためのメカニズムを提供します。このパッケージでは、インターフェース型や特定の具象型をエンコード/デコードするために、内部的に型情報を登録・管理する仕組みがあります。この型情報の登録や参照は、Register 関数や関連する内部マップ(nameToConcreteTypeconcreteTypeToName)を通じて行われます。

元の実装では、これらの型情報を管理するマップへのアクセスが同期されていませんでした。そのため、複数のゴルーチンが同時に型を登録しようとしたり、登録された型情報を参照しようとしたりすると、データ競合が発生する可能性がありました。データ競合は、プログラムの予測不能な動作、クラッシュ、または誤ったデータの読み取りにつながる深刻なバグです。

この問題は、GoのIssueトラッカーで #4214 として報告されていました。このコミットは、その報告されたデータ競合を修正することを目的としています。

前提知識の解説

Go言語におけるデータ競合 (Data Race)

データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうち少なくとも1つのアクセスが書き込みである場合に発生します。さらに、これらのアクセスが同期メカニズムによって順序付けされていない場合にデータ競合とみなされます。データ競合はGoのメモリモデルにおいて未定義の動作を引き起こし、プログラムの信頼性を著しく損ないます。

sync パッケージと同期プリミティブ

Go言語は、並行処理におけるデータ競合を防ぐために、標準ライブラリの sync パッケージに様々な同期プリミティブを提供しています。

  • sync.Mutex: 排他ロック(Mutual Exclusion Lock)を提供します。Lock()Unlock() メソッドを持ち、一度に1つのゴルーチンのみが保護されたセクションにアクセスできるようにします。読み書き両方の操作を排他的に保護する場合に適しています。
  • sync.RWMutex: 読み書きロック(Reader-Writer Mutex)を提供します。これは、読み取り操作が頻繁に行われ、書き込み操作が比較的少ない場合に効率的です。
    • RLock() / RUnlock(): 読み取りロックを取得/解放します。複数のゴルーチンが同時に読み取りロックを取得できます。
    • Lock() / Unlock(): 書き込みロックを取得/解放します。書き込みロックが取得されている間は、他のすべての読み取りロックおよび書き込みロックはブロックされます。

encoding/gob パッケージ

encoding/gob パッケージは、Goのプログラム間でGoの値をエンコード(シリアライズ)およびデコード(デシリアライズ)するためのバイナリ形式を提供します。これは、RPC(Remote Procedure Call)や永続化などの用途でよく使用されます。gob は、エンコード/デコードするデータの型情報を自動的に交換し、必要に応じて型を登録する機能を持っています。

技術的詳細

このコミットの主要な変更点は、encoding/gob パッケージ内で型情報を管理するグローバルマップ nameToConcreteTypeconcreteTypeToName へのアクセスを sync.RWMutex を使用して同期することです。

  • nameToConcreteType: 型の名前(文字列)から reflect.Type へのマッピングを保持します。
  • concreteTypeToName: reflect.Type から型名(文字列)へのマッピングを保持します。

これらのマップは、gob がインターフェース型をエンコード/デコードする際に、具象型とそれに対応する名前を解決するために使用されます。

sync.RWMutex の選択理由

sync.RWMutex が選択されたのは、これらのマップへのアクセスパターンが「読み取りが頻繁で、書き込み(登録)が比較的少ない」という特性を持つためです。

  • 読み取り操作: decode.godecodeInterface 関数や encode.goencodeInterface 関数、type_test.go のテストコードなど、既存の型情報を参照する際には RLock()RUnlock() を使用します。これにより、複数のゴルーチンが同時に型情報を安全に読み取ることができます。
  • 書き込み操作: type.goRegisterName 関数(および Register 関数から呼ばれる)のように、新しい型を登録する際には Lock()Unlock() を使用します。これにより、型情報の変更中に他の読み取りや書き込みがブロックされ、マップの一貫性が保たれます。

sync.Mutex を使用することも可能ですが、その場合、読み取り操作であっても排他ロックが必要となり、並行性が低下します。sync.RWMutex を使用することで、読み取り操作の並行性を維持しつつ、書き込み操作の安全性を確保できるため、より効率的な解決策となります。

コアとなるコードの変更箇所

以下のファイルが変更されています。

  1. src/pkg/encoding/gob/decode.go:
    • decodeInterface 関数内で nameToConcreteType マップを読み取る際に、registerLock.RLock()registerLock.RUnlock() が追加されました。
  2. src/pkg/encoding/gob/encode.go:
    • encodeInterface 関数内で concreteTypeToName マップを読み取る際に、registerLock.RLock()registerLock.RUnlock() が追加されました。
  3. src/pkg/encoding/gob/type.go:
    • registerLock sync.RWMutex がグローバル変数として追加されました。
    • RegisterName 関数内で型を登録する際に、registerLock.Lock()defer registerLock.Unlock() が追加されました。
  4. src/pkg/encoding/gob/type_test.go:
    • TestRegistrationNaming テスト関数内で nameToConcreteType マップを読み取る際に、registerLock.RLock()registerLock.RUnlock() が追加されました。これは、テスト自体がデータ競合を引き起こさないようにするため、またはテストが実際の使用状況をより正確に反映するようにするための変更です。

コアとなるコードの解説

src/pkg/encoding/gob/type.go の変更

var (
	registerLock       sync.RWMutex // 追加
	nameToConcreteType = make(map[string]reflect.Type)
	concreteTypeToName = make(map[reflect.Type]string)
)

func RegisterName(name string, value interface{}) {
	if name == "" {
		// reserved for nil
		panic("attempt to register empty name")
	}
	registerLock.Lock()   // 追加
	defer registerLock.Unlock() // 追加
	ut := userType(reflect.TypeOf(value))
	// ... (既存のロジック)
}
  • registerLock sync.RWMutex: nameToConcreteTypeconcreteTypeToName マップへのアクセスを保護するための読み書きロックが宣言されました。
  • registerLock.Lock()defer registerLock.Unlock(): RegisterName 関数は、新しい型を登録する際にこれらのマップを書き換えます。そのため、書き込みロックを取得し、関数が終了する際に確実に解放するように defer を使用しています。これにより、型登録中のマップへの排他的アクセスが保証され、他のゴルーチンからの同時書き込みや読み取りがブロックされます。

src/pkg/encoding/gob/decode.go の変更

func (dec *Decoder) decodeInterface(ityp reflect.Type, state *decoderState, p uint64) (reflect.Value, error) {
	// ... (既存のロジック)
	// The concrete type must be registered.
	registerLock.RLock()   // 追加
	typ, ok := nameToConcreteType[name]
	registerLock.RUnlock() // 追加
	if !ok {
		errorf("name not registered for interface: %q", name)
	}
	// ... (既存のロジック)
}
  • registerLock.RLock()registerLock.RUnlock(): decodeInterface 関数は、受信したデータから型名を取得し、それに対応する具象型を nameToConcreteType マップから検索します。これは読み取り操作であるため、読み取りロックを取得し、検索後に解放します。これにより、複数のデコーダが同時に型情報を読み取ることが可能になります。

src/pkg/encoding/gob/encode.go の変更

func (enc *Encoder) encodeInterface(b *bytes.Buffer, iv reflect.Value) {
	// ... (既存のロジック)
	ut := userType(iv.Elem().Type())
	registerLock.RLock()   // 追加
	name, ok := concreteTypeToName[ut.base]
	registerLock.RUnlock() // 追加
	if !ok {
		errorf("type not registered for interface: %s", ut.base)
	}
	// ... (既存のロジック)
}
  • registerLock.RLock()registerLock.RUnlock(): encodeInterface 関数は、エンコードするインターフェースの具象型に対応する名前を concreteTypeToName マップから検索します。これも読み取り操作であるため、読み取りロックを使用します。

これらの変更により、encoding/gob パッケージの型登録および参照メカニズムにおけるデータ競合が効果的に解消され、並行環境下での安定性と信頼性が向上しました。

関連リンク

参考にした情報源リンク

[インデックス 14092] ファイルの概要

このコミットは、Go言語の標準ライブラリである encoding/gob パッケージにおけるデータ競合(data race)の修正を目的としています。具体的には、Register 関数および関連する型登録メカニズムにおいて、並行アクセス時に発生しうる競合状態を解消するために、読み書きロック(sync.RWMutex)が導入されました。

コミット

commit e855fcc3073ccb4dc3fa7a3b7dc2b076e5bb54cf
Author: Rob Pike <r@golang.org>
Date:   Tue Oct 9 11:56:38 2012 +1100

    encoding/gob: fix data race in Register
    Fixes #4214.
    
    R=golang-dev, dsymonds, bradfitz
    CC=golang-dev
    https://golang.org/cl/6637047

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e855fcc3073ccb4dc3fa7a3b7dc2b076e5bb54cf

元コミット内容

encoding/gob: fix data race in Register Fixes #4214.

R=golang-dev, dsymonds, bradfitz CC=golang-dev https://golang.org/cl/6637047

変更の背景

encoding/gob パッケージは、Goのデータ構造をシリアライズおよびデシリアライズするためのメカニズムを提供します。このパッケージでは、インターフェース型や特定の具象型をエンコード/デコードするために、内部的に型情報を登録・管理する仕組みがあります。この型情報の登録や参照は、Register 関数や関連する内部マップ(nameToConcreteTypeconcreteTypeToName)を通じて行われます。

元の実装では、これらの型情報を管理するマップへのアクセスが同期されていませんでした。そのため、複数のゴルーチンが同時に型を登録しようとしたり、登録された型情報を参照しようとしたりすると、データ競合が発生する可能性がありました。データ競合は、プログラムの予測不能な動作、クラッシュ、または誤ったデータの読み取りにつながる深刻なバグです。

この問題は、GoのIssueトラッカーで #4214 として報告されていました。このコミットは、その報告されたデータ競合を修正することを目的としています。

前提知識の解説

Go言語におけるデータ競合 (Data Race)

データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうち少なくとも1つのアクセスが書き込みである場合に発生します。さらに、これらのアクセスが同期メカニズムによって順序付けされていない場合にデータ競合とみなされます。データ競合はGoのメモリモデルにおいて未定義の動作を引き起こし、プログラムの信頼性を著しく損ないます。

sync パッケージと同期プリミティブ

Go言語は、並行処理におけるデータ競合を防ぐために、標準ライブラリの sync パッケージに様々な同期プリミティブを提供しています。

  • sync.Mutex: 排他ロック(Mutual Exclusion Lock)を提供します。Lock()Unlock() メソッドを持ち、一度に1つのゴルーチンのみが保護されたセクションにアクセスできるようにします。読み書き両方の操作を排他的に保護する場合に適しています。
  • sync.RWMutex: 読み書きロック(Reader-Writer Mutex)を提供します。これは、読み取り操作が頻繁に行われ、書き込み操作が比較的少ない場合に効率的です。
    • RLock() / RUnlock(): 読み取りロックを取得/解放します。複数のゴルーチンが同時に読み取りロックを取得できます。
    • Lock() / Unlock(): 書き込みロックを取得/解放します。書き込みロックが取得されている間は、他のすべての読み取りロックおよび書き込みロックはブロックされます。

encoding/gob パッケージ

encoding/gob パッケージは、Goのプログラム間でGoの値をエンコード(シリアライズ)およびデコード(デシリアライズ)するためのバイナリ形式を提供します。これは、RPC(Remote Procedure Call)や永続化などの用途でよく使用されます。gob は、エンコード/デコードするデータの型情報を自動的に交換し、必要に応じて型を登録する機能を持っています。

技術的詳細

このコミットの主要な変更点は、encoding/gob パッケージ内で型情報を管理するグローバルマップ nameToConcreteTypeconcreteTypeToName へのアクセスを sync.RWMutex を使用して同期することです。

  • nameToConcreteType: 型の名前(文字列)から reflect.Type へのマッピングを保持します。
  • concreteTypeToName: reflect.Type から型名(文字列)へのマッピングを保持します。

これらのマップは、gob がインターフェース型をエンコード/デコードする際に、具象型とそれに対応する名前を解決するために使用されます。

sync.RWMutex の選択理由

sync.RWMutex が選択されたのは、これらのマップへのアクセスパターンが「読み取りが頻繁で、書き込み(登録)が比較的少ない」という特性を持つためです。

  • 読み取り操作: decode.godecodeInterface 関数や encode.goencodeInterface 関数、type_test.go のテストコードなど、既存の型情報を参照する際には RLock()RUnlock() を使用します。これにより、複数のゴルーチンが同時に型情報を安全に読み取ることができます。
  • 書き込み操作: type.goRegisterName 関数(および Register 関数から呼ばれる)のように、新しい型を登録する際には Lock()Unlock() を使用します。これにより、型情報の変更中に他の読み取りや書き込みがブロックされ、マップの一貫性が保たれます。

sync.Mutex を使用することも可能ですが、その場合、読み取り操作であっても排他ロックが必要となり、並行性が低下します。sync.RWMutex を使用することで、読み取り操作の並行性を維持しつつ、書き込み操作の安全性を確保できるため、より効率的な解決策となります。

コアとなるコードの変更箇所

以下のファイルが変更されています。

  1. src/pkg/encoding/gob/decode.go:
    • decodeInterface 関数内で nameToConcreteType マップを読み取る際に、registerLock.RLock()registerLock.RUnlock() が追加されました。
  2. src/pkg/encoding/gob/encode.go:
    • encodeInterface 関数内で concreteTypeToName マップを読み取る際に、registerLock.RLock()registerLock.RUnlock() が追加されました。
  3. src/pkg/encoding/gob/type.go:
    • registerLock sync.RWMutex がグローバル変数として追加されました。
    • RegisterName 関数内で型を登録する際に、registerLock.Lock()defer registerLock.Unlock() が追加されました。
  4. src/pkg/encoding/gob/type_test.go:
    • TestRegistrationNaming テスト関数内で nameToConcreteType マップを読み取る際に、registerLock.RLock()registerLock.RUnlock() が追加されました。これは、テスト自体がデータ競合を引き起こさないようにするため、またはテストが実際の使用状況をより正確に反映するようにするための変更です。

コアとなるコードの解説

src/pkg/encoding/gob/type.go の変更

var (
	registerLock       sync.RWMutex // 追加
	nameToConcreteType = make(map[string]reflect.Type)
	concreteTypeToName = make(map[reflect.Type]string)
)

func RegisterName(name string, value interface{}) {
	if name == "" {
		// reserved for nil
		panic("attempt to register empty name")
	}
	registerLock.Lock()   // 追加
	defer registerLock.Unlock() // 追加
	ut := userType(reflect.TypeOf(value))
	// ... (既存のロジック)
}
  • registerLock sync.RWMutex: nameToConcreteTypeconcreteTypeToName マップへのアクセスを保護するための読み書きロックが宣言されました。
  • registerLock.Lock()defer registerLock.Unlock(): RegisterName 関数は、新しい型を登録する際にこれらのマップを書き換えます。そのため、書き込みロックを取得し、関数が終了する際に確実に解放するように defer を使用しています。これにより、型登録中のマップへの排他的アクセスが保証され、他のゴルーチンからの同時書き込みや読み取りがブロックされます。

src/pkg/encoding/gob/decode.go の変更

func (dec *Decoder) decodeInterface(ityp reflect.Type, state *decoderState, p uint64) (reflect.Value, error) {
	// ... (既存のロジック)
	// The concrete type must be registered.
	registerLock.RLock()   // 追加
	typ, ok := nameToConcreteType[name]
	registerLock.RUnlock() // 追加
	if !ok {
		errorf("name not registered for interface: %q", name)
	}
	// ... (既存のロジック)
}
  • registerLock.RLock()registerLock.RUnlock(): decodeInterface 関数は、受信したデータから型名を取得し、それに対応する具象型を nameToConcreteType マップから検索します。これは読み取り操作であるため、読み取りロックを取得し、検索後に解放します。これにより、複数のデコーダが同時に型情報を読み取ることが可能になります。

src/pkg/encoding/gob/encode.go の変更

func (enc *Encoder) encodeInterface(b *bytes.Buffer, iv reflect.Value) {
	// ... (既存のロジック)
	ut := userType(iv.Elem().Type())
	registerLock.RLock()   // 追加
	name, ok := concreteTypeToName[ut.base]
	registerLock.RUnlock() // 追加
	if !ok {
		errorf("type not registered for interface: %s", ut.base)
	}
	// ... (既存のロジック)
}
  • registerLock.RLock()registerLock.RUnlock(): encodeInterface 関数は、エンコードするインターフェースの具象型に対応する名前を concreteTypeToName マップから検索します。これも読み取り操作であるため、読み取りロックを使用します。

これらの変更により、encoding/gob パッケージの型登録および参照メカニズムにおけるデータ競合が効果的に解消され、並行環境下での安定性と信頼性が向上しました。

関連リンク

参考にした情報源リンク