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

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

このコミットは、Go言語の標準ライブラリである sync パッケージ内の mutex.go ファイルに対する変更です。具体的には、Mutex および RWMutex の構造体とメソッドに詳細なドキュメンテーションが追加されています。これにより、これらの同期プリミティブの利用方法、セマンティクス、および注意点が明確化されました。

コミット

commit 8ba287585a2486132c604981ed1c549d24b9feed
Author: Russ Cox <rsc@golang.org>
Date:   Wed Mar 4 21:30:07 2009 -0800

    sync: add documentation
    
    R=r
    DELTA=63  (57 added, 1 deleted, 5 changed)
    OCL=25727
    CL=25727

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

https://github.com/golang/go/commit/8ba287585a2486132c604981ed1c549d24b9feed

元コミット内容

sync: add documentation

このコミットの目的は、Go言語の sync パッケージ、特に MutexRWMutex に関するドキュメンテーションを追加することです。これにより、これらの同期プリミティブの利用方法や振る舞いがより明確になります。

変更の背景

Go言語は、並行処理を言語レベルでサポートするためにゴルーチンとチャネルを導入しました。しかし、低レベルのライブラリルーチンや特定のパフォーマンス要件を持つシナリオでは、伝統的なミューテックス(相互排他ロック)のような同期プリミティブが必要となります。sync パッケージは、このような低レベルの同期機能を提供します。

このコミットが行われた2009年3月は、Go言語がまだ初期開発段階にあった時期です。言語の設計思想や標準ライブラリのAPIが固まりつつある中で、開発者がこれらの重要な同期プリミティブを正しく理解し、安全に利用できるようにするためのドキュメンテーションの整備は不可欠でした。特に、MutexRWMutex のようなプリミティブは、誤用するとデッドロックやデータ競合といった深刻な並行処理バグを引き起こす可能性があるため、そのセマンティクスを明確に記述することが求められました。

また、当時の RWMutex はまだ完全な実装ではなく、リードロックが実際にはライトロックと同じように振る舞うという既知のバグ(BUG(rsc)として明記されている)が存在していました。このような未完成な部分についても、ドキュメンテーションを通じてユーザーに正確な情報を提供する必要がありました。

前提知識の解説

1. 並行処理と同期プリミティブ

並行処理(Concurrency)とは、複数の計算が同時に進行しているように見える状態を指します。Go言語では、軽量なスレッドである「ゴルーチン(Goroutine)」と、ゴルーチン間の安全な通信を可能にする「チャネル(Channel)」によって並行処理をサポートしています。

しかし、共有リソースへのアクセスを制御するためには、同期メカニズムが必要です。同期プリミティブは、複数のゴルーチンが同時に共有データにアクセスする際に発生するデータ競合を防ぎ、処理の順序を保証するために使用されます。

2. sync パッケージ

sync パッケージは、Go言語で低レベルの同期プリミティブを提供する標準ライブラリです。これには、Mutex(相互排他ロック)、RWMutex(読み書きロック)、WaitGroupOnce などが含まれます。Goの設計思想では、可能な限りチャネルを用いた通信による同期("Don't communicate by sharing memory; share memory by communicating.")が推奨されますが、パフォーマンスが重視される場合や、より低レベルな制御が必要な場合には sync パッケージが利用されます。

3. Mutex (Mutual Exclusion Lock)

Mutex は、相互排他ロックを提供します。これは、一度に一つのゴルーチンだけが特定のコードセクション(クリティカルセクション)を実行できるようにするメカニズムです。

  • Lock(): ミューテックスをロックします。既にロックされている場合、呼び出し元のゴルーチンはロックが解放されるまでブロックされます。
  • Unlock(): ミューテックスをアンロックします。ロックされていないミューテックスをアンロックしようとすると、ランタイムエラーが発生します。 Mutex は、特定のゴルーチンに関連付けられていません。あるゴルーチンがロックし、別のゴルーチンがアンロックすることも可能です。

4. RWMutex (Reader/Writer Mutual Exclusion Lock)

RWMutex は、読み書きロックを提供します。これは、複数のリーダーが同時に共有リソースを読み取ることができる一方で、ライターは排他的にリソースにアクセスできるようにするロックです。

  • RLock(): 読み取りのためにロックします。複数のゴルーチンが同時に RLock を取得できます。
  • RUnlock(): 読み取りロックを解除します。
  • Lock(): 書き込みのためにロックします。このロックは排他的であり、他のリーダーやライターのアクセスをブロックします。
  • Unlock(): 書き込みロックを解除します。

5. アトミック操作とセマフォ

コミット内のコードには、cas (Compare And Swap)、semacquiresemreleasexadd といった低レベルの関数が使われています。これらは、Goランタイム内部でミューテックスの実装に使われるアトミック操作やセマフォ操作のプリミティブです。

  • cas(val *int32, old, new int32) bool: valold と等しい場合にのみ valnew に更新し、成功したかどうかを返します。これはアトミックに行われます。
  • xadd(val *int32, delta int32) (new int32): valdelta をアトミックに加算し、加算後の新しい値を返します。
  • semacquire(*int32): セマフォを取得します。セマフォが利用可能になるまでブロックします。
  • semrelease(*int32): セマフォを解放します。ブロックされているゴルーチンがあれば、一つを再開させます。 これらは、OSレベルの同期プリミティブやCPUの命令セットを利用して実装されており、Goの sync パッケージの基盤を形成しています。

技術的詳細

このコミットの主要な変更点は、src/lib/sync/mutex.go ファイルにGoDoc形式のドキュメンテーションが大量に追加されたことです。これにより、sync パッケージ全体、Mutex 構造体、Lock()Unlock() メソッド、RWMutex 構造体、RLock()RUnlock()Lock()Unlock() メソッドのそれぞれについて、その目的、振る舞い、および使用上の注意点が詳細に記述されました。

特筆すべきは、RWMutex の実装に関するコメントです。

// Stub implementation of r/w locks.
// This satisfies the semantics but
// is not terribly efficient.

// The next comment goes in the BUGS section of the document,
// in its own paragraph, without the (rsc) tag.

// BUG(rsc): RWMutex does not (yet) allow multiple readers;
// instead it behaves as if RLock and RUnlock were Lock and Unlock.

このコメントは、当時の RWMutex がまだ「スタブ実装」であり、効率的ではないこと、そして最も重要な点として、複数のリーダーを許可せず、RLockRUnlock が実質的に LockUnlock と同じように振る舞うという既知のバグ(BUG(rsc))が存在することを明記しています。これは、Go言語の初期開発段階における透明性と、将来的な改善の意図を示すものです。

また、RWMutex の構造体定義が type RWMutex struct { Mutex; } から type RWMutex struct { m Mutex; } に変更されています。これは、埋め込みフィールド(Mutex;)から名前付きフィールド(m Mutex;)への変更であり、これにより RWMutex の内部で Mutex がどのように利用されているかがより明確になります。機能的には大きな変更ではありませんが、コードの可読性と意図の明確化に寄与します。

全体として、このコミットは機能追加ではなく、既存の機能に対するドキュメンテーションの強化と、初期段階のGo言語における同期プリミティブの現状を正確に反映するためのものです。

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

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

  1. パッケージレベルのドキュメンテーションの追加:

    --- a/src/lib/sync/mutex.go
    +++ b/src/lib/sync/mutex.go
    @@ -2,12 +2,19 @@
     // Use of this source code is governed by a BSD-style
     // license that can be found in the LICENSE file.
     
    +// The sync package provides basic synchronization primitives
    +// such as mutual exclusion locks.  These are intended for use
    +// by low-level library routines.  Higher-level synchronization
    +// is better done via channels and communication.
     package sync
    

    sync パッケージの目的と、チャネルによる高レベルな同期が推奨される旨が記述されました。

  2. Mutex 構造体とゼロ値に関するドキュメンテーションの追加:

    --- a/src/lib/sync/mutex.go
    +++ b/src/lib/sync/mutex.go
    @@ -2,12 +2,19 @@
     // Use of this source code is governed by a BSD-style
     // license that can be found in the LICENSE file.
     
     // The sync package provides basic synchronization primitives
     // such as mutual exclusion locks.  These are intended for use
     // by low-level library routines.  Higher-level synchronization
     // is better done via channels and communication.
     package sync
     
     func cas(val *int32, old, new int32) bool
     func semacquire(*int32)
     func semrelease(*int32)
     
    +// A Mutex is a mutual exclusion lock.
    +// Mutexes can be created as part of other structures;
    +// the zero value for a Mutex is an unlocked mutex.
     type Mutex struct {
      	key int32;
      	sema int32;
    

    Mutex が相互排他ロックであること、およびゼロ値がアンロックされた状態のミューテックスであることを説明しています。

  3. Lock() メソッドに関するドキュメンテーションの追加:

    --- a/src/lib/sync/mutex.go
    +++ b/src/lib/sync/mutex.go
    @@ -23,6 +30,9 @@ func xadd(val *int32, delta int32) (new int32) {
      	panic("unreached")
      }
      
    +// Lock locks m.
    +// If the lock is already in use, the calling goroutine
    +// blocks until the mutex is available.
     func (m *Mutex) Lock() {
      	if xadd(&m.key, 1) == 1 {
      	// changed from 0 to 1; we hold lock
    

    Lock() の振る舞い(ロック取得、ブロック)が記述されました。

  4. Unlock() メソッドに関するドキュメンテーションの追加:

    --- a/src/lib/sync/mutex.go
    +++ b/src/lib/sync/mutex.go
    @@ -31,6 +41,12 @@ func (m *Mutex) Lock() {
      	semacquire(&m.sema);
      }
      
    +// Unlock unlocks m.
    +// It is a run-time error if m is not locked on entry to Unlock.
    +//
    +// A locked Mutex is not associated with a particular goroutine.
    +// It is allowed for one goroutine to lock a Mutex and then
    +// arrange for another goroutine to unlock it.
     func (m *Mutex) Unlock() {
      	if xadd(&m.key, -1) == 0 {
      	// changed from 1 to 0; no contention
    

    Unlock() の振る舞い(アンロック、ランタイムエラー条件、ゴルーチンとの関連付けがないこと)が記述されました。

  5. RWMutex のスタブ実装に関するコメントと BUG コメントの追加、および構造体定義の変更:

    --- a/src/lib/sync/mutex.go
    +++ b/src/lib/sync/mutex.go
    @@ -42,17 +58,57 @@ func (m *Mutex) Unlock() {
      // Stub implementation of r/w locks.
      // This satisfies the semantics but
      // is not terribly efficient.
    -// TODO(rsc): Real r/w locks.
     
    +// The next comment goes in the BUGS section of the document,\n
    +// in its own paragraph, without the (rsc) tag.\n
    +\n
    +// BUG(rsc): RWMutex does not (yet) allow multiple readers;\n
    +// instead it behaves as if RLock and RUnlock were Lock and Unlock.\n
    +\n
    +// An RWMutex is a reader/writer mutual exclusion lock.\n
    +// The lock can be held by an arbitrary number of readers\n
    +// or a single writer.\n
    +// RWMutexes can be created as part of other\n
    +// structures; the zero value for a RWMutex is\n
    +// an unlocked mutex.\n
     type RWMutex struct {
    -\tMutex;\n
    +\tm Mutex;\n
     }\n
     
    +// RLock locks rw for reading.\n
    +// If the lock is already locked for writing or there is a writer already waiting\n
    +// to acquire the lock, RLock blocks until the writer has released the lock.\n
     func (rw *RWMutex) RLock() {
    -\tm.Lock();\n
    +\trw.m.Lock();\n
     }\n
     
    +// RUnlock undoes a single RLock call;\n
    +// it does not affect other simultaneous readers.\n
    +// It is a run-time error if rw is not locked for reading\n
    +// on entry to RUnlock.\n
     func (rw *RWMutex) RUnlock() {
    -\tm.Unlock();\n
    +\trw.m.Unlock();\n
     }\n
     
    +// Lock locks rw for writing.\n
    +// If the lock is already locked for reading or writing,\n
    +// Lock blocks until the lock is available.\n
    +// To ensure that the lock eventually becomes available,\n
    +// a blocked Lock call excludes new readers from acquiring\n
    +// the lock.\n
    +func (rw *RWMutex) Lock() {\n
    +\trw.m.Lock();\n
    +}\n
    +\n
    +// Unlock unlocks rw for writing.\n
    +// It is a run-time error if rw is not locked for writing\n
    +// on entry to Unlock.\n
    +//\n
    +// Like for Mutexes,\n
    +// a locked RWMutex is not associated with a particular goroutine.\n
    +// It is allowed for one goroutine to RLock (Lock) an RWMutex and then\n
    +// arrange for another goroutine to RUnlock (Unlock) it.\n
    +func (rw *RWMutex) Unlock() {\n
    +\trw.m.Unlock();\n
     }\n
    

    RWMutex の概要、読み書きロックのセマンティクス、ゼロ値の振る舞いが記述されました。特に、BUG(rsc) コメントで当時の RWMutex が複数のリーダーをサポートしていないことが明記されています。また、RWMutex の内部フィールドが Mutex; から m Mutex; に変更されています。

コアとなるコードの解説

このコミットは、Go言語の sync パッケージにおける MutexRWMutex のドキュメンテーションを大幅に強化しています。

  • パッケージレベルのドキュメンテーション: sync パッケージが低レベルの同期プリミティブを提供し、高レベルな同期にはチャネルが推奨されるというGoの設計哲学が明確に示されています。これは、Goの並行処理モデルを理解する上で非常に重要な指針です。

  • Mutex のドキュメンテーション:

    • Mutex が相互排他ロックであること、および他の構造体の一部として作成できること、そしてゼロ値がアンロックされた状態のミューテックスであることが明記されました。これは、sync.Mutex を明示的に初期化する必要がないというGoの慣習を反映しています。
    • Lock() メソッドについては、ロックが既に使われている場合に呼び出し元のゴルーチンがブロックされることが説明されています。
    • Unlock() メソッドについては、ロックされていないミューテックスをアンロックしようとするとランタイムエラーになること、そしてロックされたミューテックスが特定のゴルーチンに関連付けられていないため、あるゴルーチンがロックし、別のゴルーチンがアンロックできるという重要なセマンティクスが強調されています。これは、C++の std::mutex などとは異なるGoの柔軟な設計を示しています。
  • RWMutex のドキュメンテーション:

    • RWMutex が読み書きロックであり、複数のリーダーまたは単一のライターによって保持できることが説明されています。
    • RLock()RUnlock()Lock()Unlock() のそれぞれの振る舞いが詳細に記述されています。特に、RLock() が書き込みロックによってブロックされる条件や、Lock() が新しいリーダーのロック取得を排除して最終的にロックが利用可能になることを保証するメカニズムが説明されています。
    • 最も重要な点は、BUG(rsc) コメントです。これは、当時の RWMutex の実装がまだ完全ではなく、RLockRUnlock が実際には LockUnlock と同じように振る舞い、複数のリーダーを許可しないという既知の制限を正直に開示しています。これは、Go言語の初期開発における透明性と、ユーザーへの正確な情報提供の姿勢を示しています。
    • RWMutex の構造体定義が type RWMutex struct { Mutex; } から type RWMutex struct { m Mutex; } に変更されたことで、RWMutex が内部に Mutex を「埋め込む」のではなく、「持つ」という関係が明確になり、コードの意図がより分かりやすくなりました。

このコミットは、Go言語の同期プリミティブの利用に関する初期のベストプラクティスと、当時の実装の限界を明確にする上で非常に重要な役割を果たしています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(src/sync/mutex.go): https://github.com/golang/go/blob/master/src/sync/mutex.go
  • Go言語の初期の設計に関する議論(GoのメーリングリストやIssueトラッカーなど、当時の情報源)
  • Go言語の sync パッケージに関する一般的な解説記事やチュートリアル
  • アトミック操作とセマフォに関するコンピュータサイエンスの基礎知識
  • Go言語の BUG コメントに関する慣習についての情報