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

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

このコミットは、Go言語の標準ライブラリであるsyncパッケージ内の複数のファイルにデータ競合検出のための変更を加えるものです。具体的には、以下のファイルが影響を受けています。

  • src/pkg/go/build/deps_test.go: ビルド依存関係のテストファイル。syncパッケージの依存関係にunsafeが追加されています。
  • src/pkg/sync/cond.go: sync.Cond(条件変数)の実装ファイル。
  • src/pkg/sync/mutex.go: sync.Mutex(ミューテックス)の実装ファイル。
  • src/pkg/sync/race.go: 新規追加されたデータ競合検出関連の関数を定義するファイル。raceビルドタグが有効な場合にコンパイルされます。
  • src/pkg/sync/race0.go: 新規追加されたデータ競合検出関連のスタブ関数を定義するファイル。raceビルドタグが無効な場合にコンパイルされます。
  • src/pkg/sync/rwmutex.go: sync.RWMutex(読み書きミューテックス)の実装ファイル。
  • src/pkg/sync/waitgroup.go: sync.WaitGroupの実装ファイル。

これらの変更は、Goランタイムにデータ競合検出機能を追加する大規模な変更の一部です。

コミット

commit 53390c8fc7b27bf1a14c709feae802c410ea2ae2
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Sun Oct 7 22:07:03 2012 +0400

    race: sync changes
    This is a part of a bigger change that adds data race detection feature:
    https://golang.org/cl/6456044
    
    R=rsc, minux.ma
    CC=gobot, golang-dev
    https://golang.org/cl/6529053

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

https://github.com/golang/go/commit/53390c8fc7b27bf1a14c709feae802c410ea2ae2

元コミット内容

race: sync changes これは、データ競合検出機能を追加する大規模な変更の一部です。 https://golang.org/cl/6456044

変更の背景

このコミットは、Go言語にデータ競合検出機能(Go Race Detector)を導入する一連の変更の一部です。データ競合は並行プログラミングにおける深刻なバグの一種であり、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。データ競合は予測不能な動作、プログラムのクラッシュ、データの破損などを引き起こす可能性があります。

Go Race Detectorは、実行時にデータ競合を検出するためのツールであり、開発者が並行プログラムのバグを特定し、修正するのに役立ちます。このコミットは、syncパッケージ内の同期プリミティブ(ミューテックス、条件変数、読み書きミューテックス、WaitGroup)が、データ競合検出器と適切に連携できるようにするための変更を導入しています。具体的には、これらのプリミティブがロックの取得/解放、待機/通知などの操作を行う際に、競合検出器にそのイベントを通知するメカニズムが追加されています。

コミットメッセージに記載されているhttps://golang.org/cl/6456044は、このデータ競合検出機能の導入に関する主要な変更リスト(Change List)の一つであり、システムコールに関連する競合状態に対処することを目的としていました。

前提知識の解説

データ競合 (Data Race)

データ競合は、並行プログラムにおけるバグの一種で、以下の3つの条件がすべて満たされたときに発生します。

  1. 複数のゴルーチンが同じメモリ位置にアクセスする: 2つ以上のゴルーチンが同じ変数やデータ構造を操作しようとします。
  2. 少なくとも1つのアクセスが書き込みである: 読み取り専用のアクセスだけでは競合は発生しません。少なくとも1つのゴルーチンがデータを変更しようとします。
  3. アクセスが同期メカニズムによって保護されていない: ミューテックス、セマフォ、チャネルなどの同期プリミティブが適切に使用されていないため、アクセス順序が保証されません。

データ競合が発生すると、プログラムの実行結果がアクセス順序に依存するようになり、非決定的な動作を引き起こします。これはデバッグが非常に困難な種類のバグです。

Go Race Detector

Go言語には、ビルド時に-raceフラグを付けることで有効になる組み込みのデータ競合検出器があります。この検出器は、プログラムの実行中にメモリアクセスを監視し、データ競合のパターンを検出すると警告を出力します。検出器は、メモリの読み書き、ゴルーチンの開始/終了、同期プリミティブの操作などのイベントを追跡することで機能します。

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

Goのsyncパッケージは、並行処理を安全に行うための基本的な同期プリミティブを提供します。

  • sync.Mutex: 排他ロック。共有リソースへのアクセスを一度に1つのゴルーチンに制限します。Lock()でロックを取得し、Unlock()でロックを解放します。
  • sync.Cond: 条件変数。ミューテックスと組み合わせて使用され、特定の条件が満たされるまでゴルーチンを待機させたり、条件が満たされたときに待機中のゴルーチンを通知したりするために使用されます。Wait()で待機し、Signal()またはBroadcast()で通知します。
  • sync.RWMutex: 読み書きミューテックス。複数の読み取り操作は同時に許可しますが、書き込み操作は排他的に行われます。RLock()/RUnlock()で読み取りロック、Lock()/Unlock()で書き込みロックを取得/解放します。
  • sync.WaitGroup: 複数のゴルーチンの完了を待機するために使用されます。Add()でカウンタを増やし、Done()で減らし、Wait()でカウンタがゼロになるまでブロックします。

sync/atomicパッケージ

sync/atomicパッケージは、アトミック(不可分)な操作を提供します。これにより、複数のゴルーチンが同時にアクセスしても、操作が中断されることなく完了することが保証されます。例えば、atomic.AddInt32は、32ビット整数にアトミックに値を加算します。

unsafe.Pointer

unsafe.Pointerは、任意の型のポインタを保持できる特殊なポインタ型です。Goの型システムをバイパスして、任意のメモリ位置にアクセスすることを可能にします。データ競合検出器は、メモリアドレスを追跡する必要があるため、unsafe.Pointerを使用して同期プリミティブの内部状態や関連するメモリ領域のアドレスを検出器に渡します。

ビルドタグ (+build)

Goのソースファイルには、+buildディレクティブを使用してビルドタグを指定できます。これにより、特定の環境や条件でのみファイルをコンパイルするように制御できます。このコミットでは、sync/race.goには+build raceが、sync/race0.goには+build !raceが指定されており、-raceフラグが有効な場合にsync/race.goが、無効な場合にsync/race0.goがコンパイルされるようになっています。

技術的詳細

このコミットの主要な目的は、Goのデータ競合検出器がsyncパッケージの同期プリミティブの操作を監視し、データ競合を正確に報告できるようにすることです。これを実現するために、以下の技術的アプローチが取られています。

  1. raceenabledフラグの導入:

    • sync/race.gosync/race0.goという2つの新しいファイルが導入されました。
    • sync/race.go+build raceタグを持ち、raceenabled定数をtrueに設定します。また、runtimeパッケージのRaceAcquire, RaceRelease, RaceReleaseMerge, RaceDisable, RaceEnableといった関数をラップする関数(raceAcquire, raceReleaseなど)を定義します。これらの関数は、データ競合検出器に同期イベントを通知するためのエントリポイントとなります。
    • sync/race0.go+build !raceタグを持ち、raceenabled定数をfalseに設定します。また、sync/race.goで定義された関数と同じ名前のスタブ関数を定義します。これにより、-raceフラグなしでビルドされた場合、競合検出関連のコードはコンパイルされず、パフォーマンスへの影響を最小限に抑えます。
  2. 同期プリミティブへの競合検出フックの追加:

    • sync.Cond, sync.Mutex, sync.RWMutex, sync.WaitGroupの各メソッド(Lock, Unlock, Wait, Signal, Broadcast, Add, Doneなど)に、raceenabledtrueの場合にのみ実行される条件付きのコードブロックが追加されました。
    • これらのコードブロック内では、raceAcquire, raceRelease, raceReleaseMerge, raceDisable, raceEnableといった関数が呼び出されます。
    • raceAcquire(unsafe.Pointer(m))のような呼び出しは、ミューテックスmがロックされた(取得された)ことを競合検出器に通知します。unsafe.Pointerを使用することで、ミューテックスオブジェクト自体のメモリアドレスを検出器に渡します。
    • raceRelease(unsafe.Pointer(m))は、ミューテックスmが解放されたことを通知します。
    • raceReleaseMergeは、複数の競合領域がマージされるような特定の同期パターン(例: RWMutexRUnlock)で使用されます。
    • raceDisable()raceEnable()は、一時的に競合検出を無効化/有効化するために使用されます。これは、同期プリミティブの内部処理中に、検出器が誤って競合を報告しないようにするために重要です。例えば、Cond.Wait()RWMutex.RLock()/Lock()の内部で、ロックの取得/解放の前後で競合検出を一時的に無効化し、内部のセマフォ操作などが競合として報告されないようにしています。
  3. go/buildパッケージの依存関係の更新:

    • src/pkg/go/build/deps_test.goにおいて、syncパッケージの依存関係にunsafeが追加されました。これは、syncパッケージがunsafe.Pointerを使用するようになったため、ビルドシステムがその依存関係を正しく認識するために必要です。

これらの変更により、Goの同期プリミティブは、データ競合検出器と協調して動作し、並行プログラムにおける潜在的なデータ競合をより効果的に特定できるようになります。

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

src/pkg/go/build/deps_test.go

--- a/src/pkg/go/build/deps_test.go
+++ b/src/pkg/go/build/deps_test.go
@@ -30,7 +30,7 @@ var pkgDeps = map[string][]string{
 	"errors":      {},
 	"io":          {"errors", "sync"},
 	"runtime":     {"unsafe"},
-	"sync":        {"sync/atomic"},
+	"sync":        {"sync/atomic", "unsafe"},
 	"sync/atomic": {"unsafe"},
 	"unsafe":      {},

src/pkg/sync/cond.go

Wait, Signal, BroadcastメソッドにraceenabledチェックとraceDisable/raceEnable呼び出しが追加されています。

--- a/src/pkg/sync/cond.go
+++ b/src/pkg/sync/cond.go
@@ -56,6 +56,9 @@ func NewCond(l Locker) *Cond {
 //    c.L.Unlock()\n //\n func (c *Cond) Wait() {\n+\tif raceenabled {\n+\t\traceDisable()\n+\t}\n \tc.m.Lock()\n \tif c.newSema == nil {\n \t\tc.newSema = new(uint32)\n@@ -63,6 +66,9 @@ func (c *Cond) Wait() {\n \ts := c.newSema\n \tc.newWaiters++\n \tc.m.Unlock()\n+\tif raceenabled {\n+\t\traceEnable()\n+\t}\n \tc.L.Unlock()\n \truntime_Semacquire(s)\n \tc.L.Lock()\
@@ -73,6 +79,9 @@ func (c *Cond) Wait() {\n // It is allowed but not required for the caller to hold c.L\n // during the call.\n func (c *Cond) Signal() {\n+\tif raceenabled {\n+\t\traceDisable()\n+\t}\n \tc.m.Lock()\n \tif c.oldWaiters == 0 && c.newWaiters > 0 {\n \t\t// Retire old generation; rename new to old.\n@@ -86,6 +95,9 @@ func (c *Cond) Signal() {\n \t\truntime_Semrelease(c.oldSema)\n \t}\n \tc.m.Unlock()\n+\tif raceenabled {\n+\t\traceEnable()\n+\t}\n }\n \n // Broadcast wakes all goroutines waiting on c.\n@@ -93,6 +105,9 @@ func (c *Cond) Signal() {\n // It is allowed but not required for the caller to hold c.L\n // during the call.\n func (c *Cond) Broadcast() {\n+\tif raceenabled {\n+\t\traceDisable()\n+\t}\n \tc.m.Lock()\n \t// Wake both generations.\n \tif c.oldWaiters > 0 {\n@@ -109,4 +124,7 @@ func (c *Cond) Broadcast() {\n \t\tc.newSema = nil\n \t}\n \tc.m.Unlock()\n+\tif raceenabled {\n+\t\traceEnable()\n+\t}\n }\n```

### `src/pkg/sync/mutex.go`

`Lock`と`Unlock`メソッドに`raceenabled`チェックと`raceAcquire`/`raceRelease`呼び出しが追加されています。`unsafe`パッケージがインポートされています。

```diff
--- a/src/pkg/sync/mutex.go
+++ b/src/pkg/sync/mutex.go
@@ -10,7 +10,10 @@
 // Values containing the types defined in this package should not be copied.\n package sync\n \n-import "sync/atomic"\n+import (\n+\t"sync/atomic"\n+\t"unsafe"\n+)\n \n // A Mutex is a mutual exclusion lock.\n // Mutexes can be created as part of other structures;\n@@ -38,6 +41,9 @@ const (\n func (m *Mutex) Lock() {\n \t// Fast path: grab unlocked mutex.\n \tif atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {\n+\t\tif raceenabled {\n+\t\t\traceAcquire(unsafe.Pointer(m))\n+\t\t}\n \t\treturn\n \t}\n \n@@ -61,6 +67,10 @@ func (m *Mutex) Lock() {\n \t\t\tawoke = true\n \t\t}\n \t}\n+\n+\tif raceenabled {\n+\t\t\traceAcquire(unsafe.Pointer(m))\n+\t}\n }\n \n // Unlock unlocks m.\n@@ -70,6 +80,10 @@ func (m *Mutex) Lock() {\n // It is allowed for one goroutine to lock a Mutex and then\n // arrange for another goroutine to unlock it.\n func (m *Mutex) Unlock() {\n+\tif raceenabled {\n+\t\t\traceRelease(unsafe.Pointer(m))\n+\t}\n+\n \t// Fast path: drop lock bit.\n \tnew := atomic.AddInt32(&m.state, -mutexLocked)\n \tif (new+mutexLocked)&mutexLocked == 0 {\

src/pkg/sync/race.go (新規ファイル)

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build race

package sync

import (
	"runtime"
	"unsafe"
)

const raceenabled = true

func raceAcquire(addr unsafe.Pointer) {
	runtime.RaceAcquire(addr)
}

func raceRelease(addr unsafe.Pointer) {
	runtime.RaceRelease(addr)
}

func raceReleaseMerge(addr unsafe.Pointer) {
	runtime.RaceReleaseMerge(addr)
}

func raceDisable() {
	runtime.RaceDisable()
}

func raceEnable() {
	runtime.RaceEnable()
}

src/pkg/sync/race0.go (新規ファイル)

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !race

package sync

import (
	"unsafe"
)

const raceenabled = false

func raceAcquire(addr unsafe.Pointer) {
}

func raceRelease(addr unsafe.Pointer) {
}

func raceReleaseMerge(addr unsafe.Pointer) {
}

func raceDisable() {
}

func raceEnable() {
}

src/pkg/sync/rwmutex.go

RLock, RUnlock, Lock, UnlockメソッドにraceenabledチェックとraceAcquire/raceRelease/raceReleaseMerge/raceDisable/raceEnable呼び出しが追加されています。unsafeパッケージがインポートされています。

--- a/src/pkg/sync/rwmutex.go
+++ b/src/pkg/sync/rwmutex.go
@@ -4,7 +4,10 @@
 
 package sync
 
-import "sync/atomic"\n+import (\n+\t"sync/atomic"\n+\t"unsafe"\n+)\n \n // An RWMutex is a reader/writer mutual exclusion lock.\n // The lock can be held by an arbitrary number of readers\n@@ -24,10 +27,17 @@ const rwmutexMaxReaders = 1 << 30\n \n // RLock locks rw for reading.\n func (rw *RWMutex) RLock() {\n+\tif raceenabled {\n+\t\traceDisable()\n+\t}\n \tif atomic.AddInt32(&rw.readerCount, 1) < 0 {\n \t\t// A writer is pending, wait for it.\n \t\truntime_Semacquire(&rw.readerSem)\n \t}\n+\tif raceenabled {\n+\t\traceEnable()\n+\t\traceAcquire(unsafe.Pointer(&rw.readerSem))\n+\t}\n }\n \n // RUnlock undoes a single RLock call;\n@@ -35,6 +45,10 @@ func (rw *RWMutex) RLock() {\n // It is a run-time error if rw is not locked for reading\n // on entry to RUnlock.\n func (rw *RWMutex) RUnlock() {\n+\tif raceenabled {\n+\t\traceReleaseMerge(unsafe.Pointer(&rw.writerSem))\n+\t\traceDisable()\n+\t}\n \tif atomic.AddInt32(&rw.readerCount, -1) < 0 {\n \t\t// A writer is pending.\n \t\tif atomic.AddInt32(&rw.readerWait, -1) == 0 {\n@@ -42,6 +56,9 @@ func (rw *RWMutex) RUnlock() {\n \t\t\truntime_Semrelease(&rw.writerSem)\n \t\t}\n \t}\n+\tif raceenabled {\n+\t\traceEnable()\n+\t}\n }\n \n // Lock locks rw for writing.\n@@ -51,6 +68,9 @@ func (rw *RWMutex) RUnlock() {\n // a blocked Lock call excludes new readers from acquiring\n // the lock.\n func (rw *RWMutex) Lock() {\n+\tif raceenabled {\n+\t\traceDisable()\n+\t}\n \t// First, resolve competition with other writers.\n \trw.w.Lock()\n \t// Announce to readers there is a pending writer.\n@@ -59,6 +79,11 @@ func (rw *RWMutex) Lock() {\n \tif r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {\n \t\truntime_Semacquire(&rw.writerSem)\n \t}\n+\tif raceenabled {\n+\t\traceEnable()\n+\t\traceAcquire(unsafe.Pointer(&rw.readerSem))\n+\t\traceAcquire(unsafe.Pointer(&rw.writerSem))\n+\t}\n }\n \n // Unlock unlocks rw for writing.  It is a run-time error if rw is\n@@ -68,6 +93,12 @@ func (rw *RWMutex) Lock() {\n // goroutine.  One goroutine may RLock (Lock) an RWMutex and then\n // arrange for another goroutine to RUnlock (Unlock) it.\n func (rw *RWMutex) Unlock() {\n+\tif raceenabled {\n+\t\traceRelease(unsafe.Pointer(&rw.readerSem))\n+\t\traceRelease(unsafe.Pointer(&rw.writerSem))\n+\t\traceDisable()\n+\t}\n+\n \t// Announce to readers there is no active writer.\n \tr := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)\n \t// Unblock blocked readers, if any.\n@@ -76,6 +107,9 @@ func (rw *RWMutex) Unlock() {\n \t}\n \t// Allow other writers to proceed.\n \trw.w.Unlock()\n+\tif raceenabled {\n+\t\traceEnable()\n+\t}\n }\n \n // RLocker returns a Locker interface that implements\n```

### `src/pkg/sync/waitgroup.go`

`Add`と`Wait`メソッドに`raceenabled`チェックと`raceAcquire`/`raceReleaseMerge`/`raceDisable`/`raceEnable`呼び出しが追加されています。`unsafe`パッケージがインポートされています。

```diff
--- a/src/pkg/sync/waitgroup.go
+++ b/src/pkg/sync/waitgroup.go
@@ -4,7 +4,10 @@
 
 package sync
 
-import "sync/atomic"\n+import (\n+\t"sync/atomic"\n+\t"unsafe"\n+)\n \n // A WaitGroup waits for a collection of goroutines to finish.\n // The main goroutine calls Add to set the number of\n@@ -34,6 +37,11 @@ type WaitGroup struct {\n // If the counter becomes zero, all goroutines blocked on Wait() are released.\n // If the counter goes negative, Add panics.\n func (wg *WaitGroup) Add(delta int) {\n+\tif raceenabled {\n+\t\traceReleaseMerge(unsafe.Pointer(wg))\n+\t\traceDisable()\n+\t\tdefer raceEnable()\n+\t}\n \tv := atomic.AddInt32(&wg.counter, int32(delta))\n \tif v < 0 {\n \t\tpanic(\"sync: negative WaitGroup counter\")\n@@ -57,7 +65,14 @@ func (wg *WaitGroup) Done() {\n \n // Wait blocks until the WaitGroup counter is zero.\n func (wg *WaitGroup) Wait() {\n+\tif raceenabled {\n+\t\traceDisable()\n+\t}\n \tif atomic.LoadInt32(&wg.counter) == 0 {\n+\t\tif raceenabled {\n+\t\t\t\traceEnable()\n+\t\t\t\traceAcquire(unsafe.Pointer(wg))\n+\t\t}\n \t\treturn\n \t}\n \twg.m.Lock()\n@@ -68,7 +83,15 @@ func (wg *WaitGroup) Wait() {\n \t// to avoid missing an Add.\n \tif atomic.LoadInt32(&wg.counter) == 0 {\n \t\tatomic.AddInt32(&wg.waiters, -1)\n+\t\tif raceenabled {\n+\t\t\t\traceEnable()\n+\t\t\t\traceAcquire(unsafe.Pointer(wg))\n+\t\t\t\traceDisable()\n+\t\t}\n \t\twg.m.Unlock()\n+\t\tif raceenabled {\n+\t\t\t\traceEnable()\n+\t\t}\n \t\treturn\n \t}\n \tif wg.sema == nil {\n@@ -77,4 +100,8 @@ func (wg *WaitGroup) Wait() {\n \ts := wg.sema\n \twg.m.Unlock()\n \truntime_Semacquire(s)\n+\tif raceenabled {\n+\t\traceEnable()\n+\t\traceAcquire(unsafe.Pointer(wg))\n+\t}\n }\n```

## コアとなるコードの解説

### `src/pkg/go/build/deps_test.go`の変更

`sync`パッケージが`unsafe`パッケージに依存するようになったため、ビルドシステムの依存関係定義を更新しています。これは、`sync`パッケージ内のコードが`unsafe.Pointer`を使用するようになったことによる必然的な変更です。

### `src/pkg/sync/race.go`と`src/pkg/sync/race0.go`の追加

これらのファイルは、Goのビルドタグ機能を利用して、データ競合検出が有効かどうかに応じて異なる実装を提供します。

*   **`race.go` (`-race`ビルド時)**:
    *   `const raceenabled = true`: 競合検出が有効であることを示すフラグ。
    *   `raceAcquire(addr unsafe.Pointer)`: `runtime.RaceAcquire`を呼び出します。これは、指定されたメモリアドレス`addr`がゴルーチンによって「取得」された(例: ロックが取得された)ことを競合検出器に通知します。検出器は、このアドレスに対するその後のアクセスを監視し、競合がないかチェックします。
    *   `raceRelease(addr unsafe.Pointer)`: `runtime.RaceRelease`を呼び出します。これは、指定されたメモリアドレス`addr`がゴルーチンによって「解放」された(例: ロックが解放された)ことを競合検出器に通知します。
    *   `raceReleaseMerge(addr unsafe.Pointer)`: `runtime.RaceReleaseMerge`を呼び出します。これは、複数の競合領域がマージされるような特定の同期パターン(例: `RWMutex`の`RUnlock`)で使用され、検出器にその情報を伝えます。
    *   `raceDisable()`: `runtime.RaceDisable()`を呼び出します。これは、一時的に競合検出を無効にします。同期プリミティブの内部処理中に、検出器が誤って競合を報告しないようにするために使用されます。
    *   `raceEnable()`: `runtime.RaceEnable()`を呼び出します。これは、無効化された競合検出を再度有効にします。

*   **`race0.go` (通常ビルド時)**:
    *   `const raceenabled = false`: 競合検出が無効であることを示すフラグ。
    *   すべての`race*`関数は空のスタブとして実装されています。これにより、`-race`フラグなしでビルドされた場合、これらの関数呼び出しはコンパイル時に最適化され、実行時のオーバーヘッドがなくなります。

### `sync.Cond`の変更

`Wait()`, `Signal()`, `Broadcast()`メソッドの冒頭と末尾に`raceDisable()`と`raceEnable()`の呼び出しが追加されています。
`Cond.Wait()`は、ゴルーチンが条件変数で待機する際に、内部的にセマフォ操作を行います。このセマフォ操作自体は同期メカニズムの一部ですが、競合検出器がその内部実装を誤ってデータ競合と解釈する可能性があります。そのため、`Wait()`の開始時に競合検出を一時的に無効化し、セマフォ操作が完了した後に再度有効化することで、誤検出を防ぎます。`Signal()`や`Broadcast()`も同様に、内部のセマフォ解放操作の前後で競合検出を一時的に無効化しています。

### `sync.Mutex`の変更

`Lock()`メソッドでは、ミューテックスが正常に取得された後(高速パスと低速パスの両方で)、`raceAcquire(unsafe.Pointer(m))`が呼び出されます。これにより、ミューテックス`m`がロックされたことを競合検出器に通知します。
`Unlock()`メソッドでは、ミューテックスが解放される前に`raceRelease(unsafe.Pointer(m))`が呼び出されます。これにより、ミューテックス`m`がアンロックされたことを競合検出器に通知します。
これらの通知により、競合検出器はミューテックスによって保護されているメモリ領域へのアクセスを正しく追跡し、保護されていないアクセスを検出できます。

### `sync.RWMutex`の変更

`RWMutex`は読み取りロックと書き込みロックの両方を持つため、より複雑な競合検出フックが必要です。

*   **`RLock()`**: 読み取りロックが取得される前後に`raceDisable()`と`raceEnable()`が呼び出され、`raceAcquire(unsafe.Pointer(&rw.readerSem))`が呼び出されます。これは、読み取りセマフォが取得されたことを検出器に通知します。
*   **`RUnlock()`**: 読み取りロックが解放される前後に`raceDisable()`と`raceEnable()`が呼び出され、`raceReleaseMerge(unsafe.Pointer(&rw.writerSem))`が呼び出されます。`raceReleaseMerge`は、読み取りロックの解放が書き込みロックの待機状態に影響を与える可能性があるため、検出器にその関係を伝えます。
*   **`Lock()`**: 書き込みロックが取得される前後に`raceDisable()`と`raceEnable()`が呼び出され、`raceAcquire(unsafe.Pointer(&rw.readerSem))`と`raceAcquire(unsafe.Pointer(&rw.writerSem))`が呼び出されます。これは、読み取りセマフォと書き込みセマフォの両方が取得されたことを検出器に通知します。
*   **`Unlock()`**: 書き込みロックが解放される前後に`raceDisable()`と`raceEnable()`が呼び出され、`raceRelease(unsafe.Pointer(&rw.readerSem))`と`raceRelease(unsafe.Pointer(&rw.writerSem))`が呼び出されます。これは、両方のセマフォが解放されたことを検出器に通知します。

これらのフックにより、`RWMutex`の複雑なロック状態遷移が競合検出器に正確に伝達されます。

### `sync.WaitGroup`の変更

*   **`Add(delta int)`**: `WaitGroup`のカウンタが変更される前に`raceReleaseMerge(unsafe.Pointer(wg))`と`raceDisable()`が呼び出され、関数終了時に`defer raceEnable()`で競合検出が再度有効化されます。`raceReleaseMerge`は、`WaitGroup`のカウンタ変更が待機中のゴルーチンに影響を与える可能性があるため、検出器にその関係を伝えます。
*   **`Wait()`**: `WaitGroup`がゼロになるまで待機する際に、`raceDisable()`と`raceEnable()`が適切に呼び出されます。また、`WaitGroup`がゼロになったときに`raceAcquire(unsafe.Pointer(wg))`が呼び出され、`WaitGroup`が「取得」された(待機が完了した)ことを検出器に通知します。

これらの変更により、`WaitGroup`の`Add`/`Done`/`Wait`操作が競合検出器によって正しく監視され、`WaitGroup`に関連するデータ競合が検出できるようになります。

## 関連リンク

*   GitHubコミットページ: [https://github.com/golang/go/commit/53390c8fc7b27bf1a14c709feae802c410ea2ae2](https://github.com/golang/go/commit/53390c8fc7b27bf1a14c709feae802c410ea2ae2)
*   関連するGo Change List: [https://golang.org/cl/6456044](https://golang.org/cl/6456044)
*   Go Race Detectorの公式ドキュメント (Go 1.1以降で利用可能): [https://go.dev/doc/articles/race_detector](https://go.dev/doc/articles/race_detector)

## 参考にした情報源リンク

*   appspot.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHLWKbSe--g67qi8mogu5ZF0XpbX4Oya4ZVyagX_8-Lp_5IPLEjLPS0AT1otSKxE38eoMrjPKXJtTdv-Uza2OuaOdKwN21JHSCzqmH1XtUK7IinKBh-GJE6TzK5qZXLlyBu)
*   Go Race Detectorの概念と使用方法に関する一般的な情報源 (例: Go公式ドキュメント、ブログ記事など)