[インデックス 14530] ファイルの概要
このコミットは、Go言語のランタイムにおけるデータ競合(data race)レポートのスタックトレースを改善することを目的としています。具体的には、chan
、slice
、map
などのランタイム内部で競合が発生した場合に、レポートにユーザーコードのファイルと行番号だけでなく、競合が発生した正確なランタイム関数(例: runtime.chansend
, runtime.mapaccess
)をスタックトレースの最上位フレームとして追加します。これにより、複雑な式の中で競合が発生した場合でも、問題の特定が容易になります。
コミット
- コミットハッシュ:
0ce96f9ef4533430634fe4329b640176074ef9c4
- Author: Dmitriy Vyukov dvyukov@google.com
- Date: Fri Nov 30 10:29:41 2012 +0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ce96f9ef4533430634fe4329b640176074ef9c4
元コミット内容
runtime: better stack traces in race reports
When a race happens inside of runtime (chan, slice, etc),
currently reports contain only user file:line.
If the line contains a complex expression,
it's difficult to figure out where the race exactly.
This change adds one more top frame with exact
runtime function (e.g. runtime.chansend, runtime.mapaccess).
R=golang-dev
CC=golang-dev
https://golang.org/cl/6851125
変更の背景
Go言語には、並行処理におけるデータ競合を検出するための「レース検出器(Race Detector)」が組み込まれています。しかし、これまでの実装では、chan
(チャネル)、slice
(スライス)、map
(マップ)といったGoランタイムの内部処理でデータ競合が発生した場合、生成されるレポートのスタックトレースには、競合を引き起こしたユーザーコードのファイル名と行番号しか含まれていませんでした。
例えば、ch <- x + y
のような複雑な式でチャネルへの送信が行われ、その x
や y
の評価中にデータ競合が発生した場合、レポートは単にユーザーコードのその行を指し示すだけでした。これにより、開発者は「この行のどの部分で、具体的にどのランタイム操作(チャネル送信、マップアクセスなど)が競合に関与しているのか」を特定するのが困難でした。
この問題に対処するため、本コミットでは、ランタイム内部で発生したデータ競合のレポートに、競合が発生した正確なランタイム関数(例: runtime.chansend
、runtime.mapaccess
)をスタックトレースの最上位フレームとして追加することで、デバッグの精度と効率を向上させることを目指しています。
前提知識の解説
データ競合 (Data Race)
データ競合とは、複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうち少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生する並行処理のバグです。データ競合は予測不能な動作やプログラムのクラッシュを引き起こす可能性があり、Go言語ではこれを検出するためのツールが提供されています。
Go Race Detector (レース検出器)
Go言語に組み込まれているレース検出器は、実行時にデータ競合を検出するツールです。プログラムをgo run -race
、go build -race
、go test -race
などのフラグを付けて実行することで有効になります。レース検出器は、メモリへのアクセスを監視し、競合のパターンを検出すると、詳細なレポート(スタックトレース、アクセスタイプ、ゴルーチンIDなど)を出力します。
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、チャネル操作、マップ操作、スライス操作など、Go言語の基本的な機能の多くはランタイムによって実装されています。これらのランタイム関数は通常、C言語(またはGoの内部実装言語)で書かれており、ユーザーコードからは直接呼び出されませんが、Goの組み込み操作を通じて暗黙的に実行されます。
スタックトレース (Stack Trace)
スタックトレースは、プログラムの実行中に特定の時点(エラー発生時など)で呼び出された関数のリストです。最も最近呼び出された関数がリストの最上位に表示され、その関数を呼び出した関数、さらにその関数を呼び出した関数と、呼び出し元を遡って表示されます。これにより、プログラムの実行フローを理解し、問題の根本原因を特定するのに役立ちます。
runtime.getcallerpc
runtime.getcallerpc
は、Goランタイム内部で使用される関数で、現在の関数の呼び出し元のプログラムカウンタ(PC)を取得します。PCは、実行中の命令のアドレスを示すもので、スタックトレースを構築する際にどのコードが呼び出しを行ったかを特定するために利用されます。
技術的詳細
このコミットの核心は、Goレース検出器がデータ競合を報告する際に、より詳細なコンテキスト(特にランタイム関数の情報)を含めるように変更することです。
これまでのレース検出器は、メモリへのアクセス(読み込みまたは書き込み)を監視し、競合を検出すると、そのアクセスが発生したユーザーコードのプログラムカウンタ(PC)を記録していました。しかし、チャネル操作(chansend
, chanrecv
)、マップ操作(mapaccess
, mapassign
)、スライス操作(appendslice
, growslice
)などは、ユーザーコードから見ると単一の操作に見えますが、内部的には複数のメモリアクセスを伴う複雑なランタイム関数によって実現されています。
このコミットでは、runtime.racereadpc
および runtime.racewritepc
というレース検出器の内部関数に、新たに callpc
という引数を追加しています。この callpc
は、競合が発生したランタイム関数のプログラムカウンタを明示的に渡すためのものです。
変更前は、runtime.racereadpc(addr, pc)
や runtime.racewritepc(addr, pc)
のように、アクセスされたメモリのアドレス (addr
) と、そのアクセスをトリガーしたユーザーコードのPC (pc
) のみが渡されていました。
変更後は、runtime.racereadpc(addr, callpc, pc)
や runtime.racewritepc(addr, callpc, pc)
のように、callpc
が追加されました。ここで、callpc
は、runtime.chansend
や runtime.mapaccess1
のような、実際にメモリ操作を行っているランタイム関数のアドレスを指します。
この callpc
は、memoryaccess
という新しい静的ヘルパー関数に渡されます。memoryaccess
関数は、runtime/race.go
の内部で、実際にレース検出器のコアライブラリ(runtime/race.go
の runtime/race
パッケージ)に情報を渡す役割を担っています。memoryaccess
内では、callpc
が有効な場合、runtime/race.FuncEnter
と runtime/race.FuncExit
が呼び出されます。これにより、レース検出器は、特定のランタイム関数が実行中であることを認識し、その情報をスタックトレースに含めることができるようになります。
特に注目すべきは、memoryaccess
関数内で runtime.callers(3, &callpc, 1)
が呼び出される条件です。これは、callpc
が runtime.lessstack
または runtime.mheap.arena_start
と runtime.mheap.arena_used
の範囲内にある場合に実行されます。これは、スタックの深さやヒープ領域に関連する特定の内部的な呼び出しの場合に、より正確な呼び出し元を特定するためのヒューリスティックであると考えられます。
この変更により、レースレポートのスタックトレースは、ユーザーコードの行に加えて、その行で実行された具体的なランタイム操作(例: runtime.chansend
)を最上位フレームとして表示するようになり、デバッグ情報が格段に向上します。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
-
src/pkg/runtime/chan.c
:runtime·chansend
およびruntime·closechan
関数内で呼び出されるruntime·racereadpc
およびruntime·racewritepc
に、第3引数としてそれぞれのランタイム関数自身のアドレス(例:runtime·chansend
)が追加されました。// Before: // runtime·racereadpc(c, pc); // After: runtime·racereadpc(c, pc, runtime·chansend); // Before: // runtime·racewritepc(c, runtime·getcallerpc(&c)); // After: runtime·racewritepc(c, runtime·getcallerpc(&c), runtime·closechan);
-
src/pkg/runtime/hashmap.c
:runtime·mapaccess1
,runtime·mapaccess2
,reflect·mapaccess
,runtime·mapassign1
,runtime·mapdelete
,reflect·mapassign
,runtime·mapiterinit
,runtime·mapiternext
,reflect·maplen
関数内で呼び出されるruntime·racereadpc
およびruntime·racewritepc
に、同様に第3引数としてそれぞれのランタイム関数自身のアドレスが追加されました。
-
src/pkg/runtime/race.c
:runtime·racewritepc
とruntime·racereadpc
の関数シグネチャが変更され、void *callpc
という新しい引数が追加されました。- これらの関数は、新しく導入された静的ヘルパー関数
memoryaccess
を呼び出すように変更されました。 memoryaccess
関数が追加されました。この関数は、addr
,callpc
,pc
,write
の4つの引数を取り、レース検出器のコアライブラリ (runtime∕race·Write
,runtime∕race·Read
,runtime∕race·FuncEnter
,runtime∕race·FuncExit
) を呼び出します。runtime·RaceRead
とruntime·RaceWrite
関数もmemoryaccess
を呼び出すように変更されました。
-
src/pkg/runtime/race.h
:runtime·racewritepc
とruntime·racereadpc
の関数プロトタイプが、新しいvoid *callpc
引数を含むように更新されました。
-
src/pkg/runtime/race0.c
:- レース検出器が無効な場合のダミー実装である
runtime·racewritepc
とruntime·racereadpc
の関数シグネチャも、新しいvoid *callpc
引数を含むように更新されました。USED(callpc)
が追加され、未使用の引数警告を回避しています。
- レース検出器が無効な場合のダミー実装である
-
src/pkg/runtime/slice.c
:runtime·appendslice
,runtime·appendstr
,runtime·growslice
,runtime·copy
,runtime·slicestringcopy
関数内で呼び出されるruntime·racereadpc
およびruntime·racewritepc
に、第3引数としてそれぞれのランタイム関数自身のアドレスが追加されました。
コアとなるコードの解説
この変更の核心は、runtime/race.c
に導入された memoryaccess
関数と、runtime·racereadpc
および runtime·racewritepc
のシグネチャ変更です。
runtime/race.c
の変更点:
// 変更前 (runtime·racewritepc の例):
// void
// runtime·racewritepc(void *addr, void *pc) {
// if(!onstack((uintptr)addr)) {
// m->racecall = true;
// runtime∕race·Write(g->goid-1, addr, pc);
// m->racecall = false;
// }
// }
// 変更後 (memoryaccess の導入と racewritepc/racereadpc の変更):
static void
memoryaccess(void *addr, uintptr callpc, uintptr pc, bool write) {
int64 goid;
if(!onstack((uintptr)addr)) {
m->racecall = true;
goid = g->goid-1;
if(callpc) { // callpc が渡された場合
// 特定の内部的な呼び出しの場合、より正確な呼び出し元を特定
if(callpc == (uintptr)runtime·lessstack ||
(callpc >= (uintptr)runtime·mheap.arena_start && callpc < (uintptr)runtime·mheap.arena_used))
runtime·callers(3, &callpc, 1); // スタックを遡って呼び出し元を取得
runtime∕race·FuncEnter(goid, (void*)callpc); // 関数エントリをレース検出器に通知
}
if(write)
runtime∕race·Write(goid, addr, (void*)pc); // 書き込みアクセスを通知
else
runtime∕race·Read(goid, addr, (void*)pc); // 読み込みアクセスを通知
if(callpc)
runtime∕race·FuncExit(goid); // 関数終了をレース検出器に通知
m->racecall = false;
}
}
void
runtime·racewritepc(void *addr, void *callpc, void *pc) {
memoryaccess(addr, (uintptr)callpc, (uintptr)pc, true);
}
void
runtime·racereadpc(void *addr, void *callpc, void *pc) {
memoryaccess(addr, (uintptr)callpc, (uintptr)pc, false);
}
解説:
-
memoryaccess
関数の導入:- この関数は、メモリへのアクセス(読み込みまたは書き込み)をレース検出器のコアライブラリに報告するための一元化されたエントリポイントとして機能します。
callpc
という新しい引数を受け取ります。これは、競合が発生したランタイム関数のアドレス(例:runtime.chansend
)です。callpc
がnil
でない場合(つまり、ランタイム関数からの呼び出しの場合)、runtime∕race·FuncEnter
とruntime∕race·FuncExit
が呼び出されます。これらの関数は、レース検出器の内部で、特定の関数が実行中であるというコンテキストを記録するために使用されます。これにより、レースレポートのスタックトレースに、そのランタイム関数の情報が追加されるようになります。runtime·callers(3, &callpc, 1)
の呼び出しは、特定の条件下でスタックを遡り、より正確な呼び出し元のPCを取得するためのものです。これは、レース検出器が内部的に使用するスタックフレームの処理に関連する最適化または修正と考えられます。
-
runtime·racewritepc
とruntime·racereadpc
のシグネチャ変更:- これらの関数は、
void *callpc
という新しい引数を受け取るようになりました。 - これにより、
chan.c
,hashmap.c
,slice.c
などのランタイム内部で、これらの関数を呼び出す際に、競合が発生した具体的なランタイム関数のアドレスを渡すことが可能になりました。
- これらの関数は、
影響と利点:
- より正確なスタックトレース: レースレポートに、ユーザーコードの行だけでなく、その行で実行された具体的なランタイム関数(例:
runtime.chansend
、runtime.mapaccess1
)がスタックトレースの最上位フレームとして表示されるようになります。 - デバッグの容易化: 複雑な式の中で競合が発生した場合でも、どのランタイム操作が競合に関与しているかを一目で特定できるようになり、デバッグ作業が大幅に効率化されます。
- 根本原因の特定: 開発者は、競合がユーザーコードのどの部分で、そしてGoランタイムのどの内部メカニズムによって引き起こされたのかをより深く理解できるようになります。
この変更は、Goレース検出器の診断能力を大幅に向上させ、並行処理のバグを特定し修正する開発者の能力を強化する重要な改善です。
関連リンク
参考にした情報源リンク
- Go Race Detector - The Go Programming Language
- Go Runtime Source Code (GitHub)
- Data Race - Wikipedia
- Program counter - Wikipedia
- Go: The Race Detector - YouTube (Go Conference 2012) (コミット日付が2012年であるため、当時の情報源として関連性が高い可能性があります)
- Go: The Race Detector - Google I/O 2013 (レース検出器のより詳細な説明)```markdown
[インデックス 14530] ファイルの概要
このコミットは、Go言語のランタイムにおけるデータ競合(data race)レポートのスタックトレースを改善することを目的としています。具体的には、chan
、slice
、map
などのランタイム内部で競合が発生した場合に、レポートにユーザーコードのファイルと行番号だけでなく、競合が発生した正確なランタイム関数(例: runtime.chansend
, runtime.mapaccess
)をスタックトレースの最上位フレームとして追加します。これにより、複雑な式の中で競合が発生した場合でも、問題の特定が容易になります。
コミット
- コミットハッシュ:
0ce96f9ef4533430634fe4329b640176074ef9c4
- Author: Dmitriy Vyukov dvyukov@google.com
- Date: Fri Nov 30 10:29:41 2012 +0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ce96f9ef4533430634fe4329b640176074ef9c4
元コミット内容
runtime: better stack traces in race reports
When a race happens inside of runtime (chan, slice, etc),
currently reports contain only user file:line.
If the line contains a complex expression,
it's difficult to figure out where the race exactly.
This change adds one more top frame with exact
runtime function (e.g. runtime.chansend, runtime.mapaccess).
R=golang-dev
CC=golang-dev
https://golang.org/cl/6851125
変更の背景
Go言語には、並行処理におけるデータ競合を検出するための「レース検出器(Race Detector)」が組み込まれています。しかし、これまでの実装では、chan
(チャネル)、slice
(スライス)、map
(マップ)といったGoランタイムの内部処理でデータ競合が発生した場合、生成されるレポートのスタックトレースには、競合を引き起こしたユーザーコードのファイル名と行番号しか含まれていませんでした。
例えば、ch <- x + y
のような複雑な式でチャネルへの送信が行われ、その x
や y
の評価中にデータ競合が発生した場合、レポートは単にユーザーコードのその行を指し示すだけでした。これにより、開発者は「この行のどの部分で、具体的にどのランタイム操作(チャネル送信、マップアクセスなど)が競合に関与しているのか」を特定するのが困難でした。
この問題に対処するため、本コミットでは、ランタイム内部で発生したデータ競合のレポートに、競合が発生した正確なランタイム関数(例: runtime.chansend
、runtime.mapaccess
)をスタックトレースの最上位フレームとして追加することで、デバッグの精度と効率を向上させることを目指しています。
前提知識の解説
データ競合 (Data Race)
データ競合とは、複数のゴルーチンが同時に同じメモリ位置にアクセスし、そのうち少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生する並行処理のバグです。データ競合は予測不能な動作やプログラムのクラッシュを引き起こす可能性があり、Go言語ではこれを検出するためのツールが提供されています。
Go Race Detector (レース検出器)
Go言語に組み込まれているレース検出器は、実行時にデータ競合を検出するツールです。プログラムをgo run -race
、go build -race
、go test -race
などのフラグを付けて実行することで有効になります。レース検出器は、メモリへのアクセスを監視し、競合のパターンを検出すると、詳細なレポート(スタックトレース、アクセスタイプ、ゴルーチンIDなど)を出力します。
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、チャネル操作、マップ操作、スライス操作など、Go言語の基本的な機能の多くはランタイムによって実装されています。これらのランタイム関数は通常、C言語(またはGoの内部実装言語)で書かれており、ユーザーコードからは直接呼び出されませんが、Goの組み込み操作を通じて暗黙的に実行されます。
スタックトレース (Stack Trace)
スタックトレースは、プログラムの実行中に特定の時点(エラー発生時など)で呼び出された関数のリストです。最も最近呼び出された関数がリストの最上位に表示され、その関数を呼び出した関数、さらにその関数を呼び出した関数と、呼び出し元を遡って表示されます。これにより、プログラムの実行フローを理解し、問題の根本原因を特定するのに役立ちます。
runtime.getcallerpc
runtime.getcallerpc
は、Goランタイム内部で使用される関数で、現在の関数の呼び出し元のプログラムカウンタ(PC)を取得します。PCは、実行中の命令のアドレスを示すもので、スタックトレースを構築する際にどのコードが呼び出しを行ったかを特定するために利用されます。
技術的詳細
このコミットの核心は、Goレース検出器がデータ競合を報告する際に、より詳細なコンテキスト(特にランタイム関数の情報)を含めるように変更することです。
これまでのレース検出器は、メモリへのアクセス(読み込みまたは書き込み)を監視し、競合を検出すると、そのアクセスが発生したユーザーコードのプログラムカウンタ(PC)を記録していました。しかし、チャネル操作(chansend
, chanrecv
)、マップ操作(mapaccess
, mapassign
)、スライス操作(appendslice
, growslice
)などは、ユーザーコードから見ると単一の操作に見えますが、内部的には複数のメモリアクセスを伴う複雑なランタイム関数によって実現されています。
このコミットでは、runtime.racereadpc
および runtime.racewritepc
というレース検出器の内部関数に、新たに callpc
という引数を追加しています。この callpc
は、競合が発生したランタイム関数のプログラムカウンタを明示的に渡すためのものです。
変更前は、runtime.racereadpc(addr, pc)
や runtime.racewritepc(addr, pc)
のように、アクセスされたメモリのアドレス (addr
) と、そのアクセスをトリガーしたユーザーコードのPC (pc
) のみが渡されていました。
変更後は、runtime.racereadpc(addr, callpc, pc)
や runtime.racewritepc(addr, callpc, pc)
のように、callpc
が追加されました。ここで、callpc
は、runtime.chansend
や runtime.mapaccess1
のような、実際にメモリ操作を行っているランタイム関数のアドレスを指します。
この callpc
は、memoryaccess
という新しい静的ヘルパー関数に渡されます。memoryaccess
関数は、runtime/race.go
の内部で、実際にレース検出器のコアライブラリ(runtime/race.go
の runtime/race
パッケージ)に情報を渡す役割を担っています。memoryaccess
内では、callpc
が有効な場合、runtime/race.FuncEnter
と runtime/race.FuncExit
が呼び出されます。これにより、レース検出器は、特定のランタイム関数が実行中であることを認識し、その情報をスタックトレースに含めることができるようになります。
特に注目すべきは、memoryaccess
関数内で runtime.callers(3, &callpc, 1)
が呼び出される条件です。これは、callpc
が runtime.lessstack
または runtime.mheap.arena_start
と runtime.mheap.arena_used
の範囲内にある場合に実行されます。これは、スタックの深さやヒープ領域に関連する特定の内部的な呼び出しの場合に、より正確な呼び出し元を特定するためのヒューリスティックであると考えられます。
この変更により、レースレポートのスタックトレースは、ユーザーコードの行に加えて、その行で実行された具体的なランタイム操作(例: runtime.chansend
)を最上位フレームとして表示するようになり、デバッグ情報が格段に向上します。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
-
src/pkg/runtime/chan.c
:runtime·chansend
およびruntime·closechan
関数内で呼び出されるruntime·racereadpc
およびruntime·racewritepc
に、第3引数としてそれぞれのランタイム関数自身のアドレス(例:runtime·chansend
)が追加されました。// Before: // runtime·racereadpc(c, pc); // After: runtime·racereadpc(c, pc, runtime·chansend); // Before: // runtime·racewritepc(c, runtime·getcallerpc(&c)); // After: runtime·racewritepc(c, runtime·getcallerpc(&c), runtime·closechan);
-
src/pkg/runtime/hashmap.c
:runtime·mapaccess1
,runtime·mapaccess2
,reflect·mapaccess
,runtime·mapassign1
,runtime·mapdelete
,reflect·mapassign
,runtime·mapiterinit
,runtime·mapiternext
,reflect·maplen
関数内で呼び出されるruntime·racereadpc
およびruntime·racewritepc
に、同様に第3引数としてそれぞれのランタイム関数自身のアドレスが追加されました。
-
src/pkg/runtime/race.c
:runtime·racewritepc
とruntime·racereadpc
の関数シグネチャが変更され、void *callpc
という新しい引数が追加されました。- これらの関数は、新しく導入された静的ヘルパー関数
memoryaccess
を呼び出すように変更されました。 memoryaccess
関数が追加されました。この関数は、addr
,callpc
,pc
,write
の4つの引数を取り、レース検出器のコアライブラリ (runtime∕race·Write
,runtime∕race·Read
,runtime∕race·FuncEnter
,runtime∕race·FuncExit
) を呼び出します。runtime·RaceRead
とruntime·RaceWrite
関数もmemoryaccess
を呼び出すように変更されました。
-
src/pkg/runtime/race.h
:runtime·racewritepc
とruntime·racereadpc
の関数プロトタイプが、新しいvoid *callpc
引数を含むように更新されました。
-
src/pkg/runtime/race0.c
:- レース検出器が無効な場合のダミー実装である
runtime·racewritepc
とruntime·racereadpc
の関数シグネチャも、新しいvoid *callpc
引数を含むように更新されました。USED(callpc)
が追加され、未使用の引数警告を回避しています。
- レース検出器が無効な場合のダミー実装である
-
src/pkg/runtime/slice.c
:runtime·appendslice
,runtime·appendstr
,runtime·growslice
,runtime·copy
,runtime·slicestringcopy
関数内で呼び出されるruntime·racereadpc
およびruntime·racewritepc
に、第3引数としてそれぞれのランタイム関数自身のアドレスが追加されました。
コアとなるコードの解説
この変更の核心は、runtime/race.c
に導入された memoryaccess
関数と、runtime·racereadpc
および runtime·racewritepc
のシグネチャ変更です。
runtime/race.c
の変更点:
// 変更前 (runtime·racewritepc の例):
// void
// runtime·racewritepc(void *addr, void *pc) {
// if(!onstack((uintptr)addr)) {
// m->racecall = true;
// runtime∕race·Write(g->goid-1, addr, pc);
// m->racecall = false;
// }
// }
// 変更後 (memoryaccess の導入と racewritepc/racereadpc の変更):
static void
memoryaccess(void *addr, uintptr callpc, uintptr pc, bool write) {
int64 goid;
if(!onstack((uintptr)addr)) {
m->racecall = true;
goid = g->goid-1;
if(callpc) { // callpc が渡された場合
// 特定の内部的な呼び出しの場合、より正確な呼び出し元を特定
if(callpc == (uintptr)runtime·lessstack ||
(callpc >= (uintptr)runtime·mheap.arena_start && callpc < (uintptr)runtime·mheap.arena_used))
runtime·callers(3, &callpc, 1); // スタックを遡って呼び出し元を取得
runtime∕race·FuncEnter(goid, (void*)callpc); // 関数エントリをレース検出器に通知
}
if(write)
runtime∕race·Write(goid, addr, (void*)pc); // 書き込みアクセスを通知
else
runtime∕race·Read(goid, addr, (void*)pc); // 読み込みアクセスを通知
if(callpc)
runtime∕race·FuncExit(goid); // 関数終了をレース検出器に通知
m->racecall = false;
}
}
void
runtime·racewritepc(void *addr, void *callpc, void *pc) {
memoryaccess(addr, (uintptr)callpc, (uintptr)pc, true);
}
void
runtime·racereadpc(void *addr, void *callpc, void *pc) {
memoryaccess(addr, (uintptr)callpc, (uintptr)pc, false);
}
解説:
-
memoryaccess
関数の導入:- この関数は、メモリへのアクセス(読み込みまたは書き込み)をレース検出器のコアライブラリに報告するための一元化されたエントリポイントとして機能します。
callpc
という新しい引数を受け取ります。これは、競合が発生したランタイム関数のアドレス(例:runtime.chansend
)です。callpc
がnil
でない場合(つまり、ランタイム関数からの呼び出しの場合)、runtime∕race·FuncEnter
とruntime∕race·FuncExit
が呼び出されます。これらの関数は、レース検出器の内部で、特定の関数が実行中であるというコンテキストを記録するために使用されます。これにより、レースレポートのスタックトレースに、そのランタイム関数の情報が追加されるようになります。runtime·callers(3, &callpc, 1)
の呼び出しは、特定の条件下でスタックを遡り、より正確な呼び出し元のPCを取得するためのものです。これは、レース検出器が内部的に使用するスタックフレームの処理に関連する最適化または修正と考えられます。
-
runtime·racewritepc
とruntime·racereadpc
のシグネチャ変更:- これらの関数は、
void *callpc
という新しい引数を受け取るようになりました。 - これにより、
chan.c
,hashmap.c
,slice.c
などのランタイム内部で、これらの関数を呼び出す際に、競合が発生した具体的なランタイム関数のアドレスを渡すことが可能になりました。
- これらの関数は、
影響と利点:
- より正確なスタックトレース: レースレポートに、ユーザーコードの行だけでなく、その行で実行された具体的なランタイム関数(例:
runtime.chansend
、runtime.mapaccess1
)がスタックトレースの最上位フレームとして表示されるようになります。 - デバッグの容易化: 複雑な式の中で競合が発生した場合でも、どのランタイム操作が競合に関与しているかを一目で特定できるようになり、デバッグ作業が大幅に効率化されます。
- 根本原因の特定: 開発者は、競合がユーザーコードのどの部分で、そしてGoランタイムのどの内部メカニズムによって引き起こされたのかをより深く理解できるようになります。
この変更は、Goレース検出器の診断能力を大幅に向上させ、並行処理のバグを特定し修正する開発者の能力を強化する重要な改善です。
関連リンク
参考にした情報源リンク
- Go Race Detector - The Go Programming Language
- Go Runtime Source Code (GitHub)
- Data Race - Wikipedia
- Program counter - Wikipedia
- Go: The Race Detector - YouTube (Go Conference 2012) (コミット日付が2012年であるため、当時の情報源として関連性が高い可能性があります)
- Go: The Race Detector - Google I/O 2013 (レース検出器のより詳細な説明)