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

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

このコミットは、Go言語の標準ライブラリの一部である expvar パッケージにおける同期メカニズムの改善に関するものです。具体的には、expvar パッケージが提供する変数型 (Int, Float, String) の String() メソッド(値の読み取り)と Set() メソッド(値の書き込み)における並行処理の安全性を向上させるため、sync.Mutex の使用を sync.RWMutex に変更し、String 型には新たにロックを追加しています。

コミット

commit 63e383cff81a590c36be0791ebdbc5fc50b98faf
Author: David Symonds <dsymonds@golang.org>
Date:   Tue Mar 6 09:13:26 2012 +1100

    expvar: add locking to String, and use RWMutex properly throughout.
    
    R=bradfitz
    CC=golang-dev
    https://golang.org/cl/5754043

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

https://github.com/golang/go/commit/63e383cff81a590c36be0791ebdbc5fc50b98faf

元コミット内容

expvar: add locking to String, and use RWMutex properly throughout.

R=bradfitz
CC=golang-dev
https://golang.org/cl/5754043

変更の背景

expvar パッケージは、Goプログラムの内部状態(変数)をHTTP経由で公開するためのメカニズムを提供します。これにより、外部の監視ツールやデバッグツールがプログラムの実行中にその状態をリアルタイムで取得できるようになります。これらの公開される変数は、複数のGoroutine(Goの軽量スレッド)から同時に読み書きされる可能性があります。

このコミット以前は、Int および Float 型の String() メソッド(値の読み取り)では sync.Mutex が使用されており、String 型にはそもそもロックがありませんでした。

  1. sync.Mutex の非効率性: sync.Mutex は排他ロックであり、一度に1つのGoroutineしかロックを保持できません。これは、読み取り操作であっても、他の読み取り操作や書き込み操作をすべてブロックすることを意味します。expvar の変数は頻繁に読み取られる(監視される)ことが想定されるため、読み取り操作が互いにブロックし合うのは非効率的でした。特に、書き込みが稀で読み取りが頻繁なシナリオでは、この排他ロックはパフォーマンスのボトルネックとなる可能性がありました。

  2. String 型の競合状態: String 型にはロックが全く実装されていなかったため、複数のGoroutineが同時に String 型の値を読み書きしようとすると、競合状態(Race Condition)が発生し、データの破損や予期せぬ動作を引き起こす可能性がありました。これは重大なバグであり、修正が必要でした。

これらの問題に対処するため、このコミットでは sync.RWMutex の導入と、String 型への適切なロックの追加が行われました。

前提知識の解説

Go言語の sync パッケージ

Go言語の sync パッケージは、並行処理における同期プリミティブを提供します。

  • sync.Mutex:

    • 「相互排他ロック」とも呼ばれます。
    • Lock() メソッドでロックを取得し、Unlock() メソッドでロックを解放します。
    • 一度に1つのGoroutineのみがロックを保持できます。
    • ロックが取得されている間は、他のすべてのGoroutineは Lock() を呼び出すとブロックされ、ロックが解放されるまで待機します。
    • 読み取りと書き込みの両方に対して排他制御が必要な場合にシンプルで効果的ですが、読み取りが頻繁に行われる場合には並行性が低下します。
  • sync.RWMutex:

    • 「読み書きロック(Reader-Writer Mutex)」とも呼ばれます。
    • sync.Mutex よりも柔軟なロックメカニズムを提供します。
    • 読み取りロック (Reader Lock): RLock() で取得し、RUnlock() で解放します。複数のGoroutineが同時に読み取りロックを保持できます。つまり、複数のGoroutineが同時にデータを読み取ることができます。
    • 書き込みロック (Writer Lock): Lock() で取得し、Unlock() で解放します。書き込みロックは排他的であり、一度に1つのGoroutineしか書き込みロックを保持できません。書き込みロックが取得されている間は、他のすべての読み取りロックおよび書き込みロックの取得はブロックされます。
    • 読み取り操作が書き込み操作よりもはるかに頻繁に行われるシナリオで、並行性を大幅に向上させることができます。

競合状態 (Race Condition)

複数のGoroutineが共有リソース(変数、データ構造など)に同時にアクセスし、少なくとも1つのGoroutineがそのリソースを変更する際に、アクセス順序によって結果が非決定的に変わってしまう状態を指します。競合状態は、プログラムのバグの一般的な原因であり、デバッグが困難な場合があります。ロックなどの同期メカニズムを使用することで、競合状態を防ぐことができます。

expvar パッケージ

Go言語の expvar パッケージは、実行中のGoプログラムの内部変数をHTTP経由で公開するためのシンプルなインターフェースを提供します。これにより、プログラムの稼働中にその状態を外部から監視したり、デバッグ情報を取得したりすることが容易になります。公開される変数は、Int, Float, String, Map などの型で表現されます。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. Int および Float 型の mu フィールドの変更:

    • type Int struct { i int64; mu sync.Mutex }type Int struct { i int64; mu sync.RWMutex } に変更されました。
    • type Float struct { f float64; mu sync.Mutex }type Float struct { f float64; mu sync.RWMutex } に変更されました。
    • これにより、これらの型の内部状態を保護するためのロックが sync.Mutex から sync.RWMutex にアップグレードされました。
  2. Int および Float 型の String() メソッドの変更:

    • v.mu.Lock()defer v.mu.Unlock()v.mu.RLock()defer v.mu.RUnlock() に変更されました。
    • String() メソッドは変数の値を読み取るだけの操作であるため、書き込みロック(Lock())ではなく読み取りロック(RLock())を使用するように変更されました。これにより、複数のGoroutineが同時に IntFloat の値を読み取ることが可能になり、読み取り操作の並行性が向上します。
  3. String 型への mu フィールドの追加とロックの導入:

    • type String struct { s string }type String struct { s string; mu sync.RWMutex } に変更されました。
    • String 型の String() メソッドには、v.mu.RLock()defer v.mu.RUnlock() が追加されました。これにより、String 型の値を読み取る際に読み取りロックが取得され、競合状態が防止されます。
    • String 型の Set() メソッドには、v.mu.Lock()defer v.mu.Unlock() が追加されました。これにより、String 型の値を書き込む際に書き込みロックが取得され、書き込み操作の排他性が保証され、競合状態が防止されます。

これらの変更により、expvar パッケージで公開される変数の読み取り操作はより並行的に実行できるようになり、書き込み操作は引き続き排他的に実行されることでデータの整合性が保たれます。特に、String 型にロックが追加されたことで、これまで存在した競合状態の脆弱性が解消されました。

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

変更は src/pkg/expvar/expvar.go ファイルに集中しています。

--- a/src/pkg/expvar/expvar.go
+++ b/src/pkg/expvar/expvar.go
@@ -41,12 +41,12 @@ type Var interface {
 // Int is a 64-bit integer variable that satisfies the Var interface.
 type Int struct {
 	i  int64
-	mu sync.Mutex
+	mu sync.RWMutex
 }
 
 func (v *Int) String() string {
-\tv.mu.Lock()\n-\tdefer v.mu.Unlock()\n+\tv.mu.RLock()\n+\tdefer v.mu.RUnlock()\n 	return strconv.FormatInt(v.i, 10)
 }
 
@@ -65,12 +65,12 @@ func (v *Int) Set(value int64) {
 // Float is a 64-bit float variable that satisfies the Var interface.
 type Float struct {
 	f  float64
-	mu sync.Mutex
+	mu sync.RWMutex
 }
 
 func (v *Float) String() string {
-\tv.mu.Lock()\n-\tdefer v.mu.Unlock()\n+\tv.mu.RLock()\n+\tdefer v.mu.RUnlock()\n 	return strconv.FormatFloat(v.f, 'g', -1, 64)
 }
 
@@ -188,12 +188,21 @@ func (v *Map) Do(f func(KeyValue)) {
 
 // String is a string variable, and satisfies the Var interface.
 type String struct {
-\ts string
+\ts  string
+\tmu sync.RWMutex
 }
 
-func (v *String) String() string { return strconv.Quote(v.s) }
+func (v *String) String() string {
+\tv.mu.RLock()\n+\tdefer v.mu.RUnlock()\n+\treturn strconv.Quote(v.s)\n+}
 
-func (v *String) Set(value string) { v.s = value }
+func (v *String) Set(value string) {
+\tv.mu.Lock()\n+\tdefer v.mu.Unlock()\n+\tv.s = value
+}\n 
 // Func implements Var by calling the function
 // and formatting the returned value using JSON.

コアとなるコードの解説

Int および Float 型の変更

  • mu sync.Mutex から mu sync.RWMutex への変更:

    • IntFloat の構造体定義において、排他ロック (sync.Mutex) を読み書きロック (sync.RWMutex) に置き換えています。これにより、これらの変数の読み取り操作と書き込み操作をより効率的に同期できるようになります。
  • String() メソッド内のロック変更:

    • v.mu.Lock()defer v.mu.Unlock()v.mu.RLock()defer v.mu.RUnlock() に変更されています。
    • String() メソッドは変数の現在の値を文字列として返す「読み取り」操作です。sync.RWMutexRLock() を使用することで、複数のGoroutineが同時にこのメソッドを呼び出して値を読み取ることが可能になります。これは、sync.MutexLock() が読み取り操作であっても他のすべての操作をブロックしていたのと対照的で、読み取りが頻繁な場合のパフォーマンスが向上します。

String 型の変更

  • mu sync.RWMutex フィールドの追加:

    • String 構造体に mu sync.RWMutex フィールドが追加されました。これにより、String 型の内部状態 (s フィールド) を保護するための同期メカニズムが導入されました。このコミット以前は、String 型にはロックが全くなく、競合状態の脆弱性がありました。
  • String() メソッドへのロックの追加:

    • String() メソッドは、String 型の値を引用符で囲んだ文字列として返す「読み取り」操作です。
    • v.mu.RLock()defer v.mu.RUnlock() が追加され、読み取りロックが適切に取得・解放されるようになりました。これにより、複数のGoroutineが同時に String 型の値を安全に読み取れるようになります。
  • Set() メソッドへのロックの追加:

    • Set() メソッドは、String 型の内部値 (s フィールド) を新しい値に更新する「書き込み」操作です。
    • v.mu.Lock()defer v.mu.Unlock() が追加され、書き込みロックが適切に取得・解放されるようになりました。書き込みロックは排他的であるため、Set() メソッドが実行されている間は、他の読み取り操作や書き込み操作はブロックされ、データの整合性が保証されます。

これらの変更により、expvar パッケージの Int, Float, String 型は、並行環境下での安全性が大幅に向上し、特に読み取り操作の並行性が改善されました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (sync および expvar パッケージ)
  • Go言語のソースコード (特に src/pkg/expvar/expvar.go の変更履歴)
  • Go言語における並行処理と同期メカニズムに関する一般的な知識