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

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

このコミットは、Go言語の公式ドキュメントから誤って削除された「Data Race Detector」のマニュアルを復元するものです。具体的には、doc/articles/race_detector.html ファイルを新規追加し、doc/docs.html からこのマニュアルへのリンクを修正しています。

コミット

commit ae5bed04c3b2d2bd752485794557021b6ce954b3
Author: Andrew Gerrand <adg@golang.org>
Date:   Tue Feb 11 09:26:34 2014 +1100

    doc: restore race detector manual
    
    This got deleted mistakenly during the content re-org.
    
    Fixes #7275.
    
    LGTM=dvyukov
    R=golang-codereviews, minux.ma, dvyukov
    CC=golang-codereviews
    https://golang.org/cl/60850043

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

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

元コミット内容

doc: restore race detector manual

This got deleted mistakenly during the content re-org.

Fixes #7275.

LGTM=dvyukov
R=golang-codereviews, minux.ma, dvyukov
CC=golang-codereviews
https://golang.org/cl/60850043

変更の背景

このコミットの背景には、Go言語のドキュメントコンテンツの再編成(content re-org)があります。その過程で、Goのデータ競合検出器(Data Race Detector)に関する重要なマニュアルが誤って削除されてしまいました。この削除は、Issue #7275として報告され、本コミットはその問題を修正し、マニュアルを元の状態に戻すことを目的としています。データ競合検出器はGoの並行処理におけるバグの特定に不可欠なツールであるため、そのマニュアルが利用できない状態はユーザーにとって大きな不便となります。

前提知識の解説

データ競合 (Data Race)

データ競合とは、複数のゴルーチン(Goにおける軽量スレッド)が同時に同じメモリ領域にアクセスし、そのうち少なくとも1つのアクセスが書き込みである場合に発生する競合状態のことです。データ競合が発生すると、プログラムの動作が予測不能になったり、クラッシュしたり、メモリが破損したりする可能性があります。これは並行プログラミングにおける最も一般的なバグの一つであり、デバッグが非常に困難です。

Go言語のメモリモデルでは、データ競合が発生しないようにプログラムを記述することが求められます。具体的には、共有変数へのアクセスはミューテックス(sync.Mutex)やチャネル(chan)などの同期プリミティブを用いて適切に保護する必要があります。

Goのデータ競合検出器 (Go Data Race Detector)

Go言語には、このようなデータ競合を検出するための組み込みツールが用意されています。これは、プログラムの実行時にメモリへのアクセスを監視し、データ競合のパターンを検出すると警告を出すものです。このツールは、go test -racego run -racego build -racego install -race のように、go コマンドに -race フラグを追加することで有効にできます。

検出器がデータ競合を検出すると、競合が発生した場所のスタックトレース、関連するゴルーチンが作成された場所のスタックトレースなどを含む詳細なレポートを出力します。これにより、開発者はデータ競合の原因を特定し、修正することができます。

ビルドタグ (Build Tags)

Goのビルドタグは、コンパイル時に特定のファイルやコードブロックを含めるか除外するかを制御するためのメカニズムです。ソースファイルの先頭に // +build tag_name の形式でコメントを記述することで使用します。例えば、-race フラグを付けてビルドすると、race というビルドタグが自動的に有効になります。これにより、データ競合検出器が有効な場合にのみ実行されるコードや、逆にデータ競合検出器が有効な場合には除外されるコードを記述することが可能になります。これは、特定のテストがデータ競合検出器の下でタイムアウトしたり、実行時間が長くなったりする場合に、そのテストをスキップするために利用されることがあります。

技術的詳細

このコミットは、主にGoのドキュメント構造と、データ競合検出器のマニュアルの内容に関するものです。

ドキュメント構造の変更

Goのドキュメントは、doc/ ディレクトリ以下に配置されています。以前は、データ競合検出器に関する情報はブログ記事として提供されていましたが、このコミットではより詳細なマニュアルとして doc/articles/race_detector.html に分離・復元されています。

doc/docs.html は、Goのドキュメントのインデックスページのような役割を果たしており、様々なドキュメントや記事へのリンクがまとめられています。このコミットでは、doc/docs.html 内のデータ競合検出器へのリンクが、ブログ記事へのリンクから新しく復元されたマニュアル (/doc/articles/race_detector.html) へのリンクに更新されています。これにより、ユーザーはより包括的な情報にアクセスできるようになります。

race_detector.html の内容

復元された race_detector.html は、データ競合検出器の利用方法、レポート形式、オプション、テストからの除外方法、一般的なデータ競合の例とその修正方法、サポートされるシステム、実行時のオーバーヘッドなど、多岐にわたる情報を提供しています。

特に重要な点は以下の通りです。

  • 使用方法: go test -race, go run -race, go build -race, go install -race コマンドによる検出器の有効化。
  • レポート形式: 検出されたデータ競合の詳細なレポート(スタックトレース、ゴルーチン生成場所など)。
  • GORACE 環境変数: log_path, exitcode, strip_path_prefix, history_size, halt_on_error などのオプションによる検出器の挙動制御。
  • テストの除外: // +build !race ビルドタグを用いた、データ競合検出器有効時の特定のコードやテストの除外方法。
  • 一般的なデータ競合の例:
    • ループカウンタの競合 (for i := 0; i < N; i++ { go func() { fmt.Println(i) }() のようなパターン)
    • 誤って共有された変数 (err 変数の再利用による競合)
    • 保護されていないグローバル変数 (マップへの同時アクセス)
    • プリミティブ型の保護されていない変数 (int64 などのアトミックでないアクセス) これらの例は、具体的なコードスニペットと、それを修正するための適切なGoの並行処理パターン(ローカルコピー、sync.Mutexsync/atomic パッケージの使用など)を示しています。
  • サポートされるシステム: darwin/amd64, linux/amd64, windows/amd64 での動作保証。
  • 実行時のオーバーヘッド: メモリ使用量が5-10倍、実行時間が2-20倍に増加する可能性。これは、検出器が実行時に詳細な監視を行うために避けられないコストです。

このマニュアルは、Go開発者がデータ競合の問題を理解し、効果的にデバッグするための包括的なガイドとして機能します。

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

--- a/doc/articles/race_detector.html
+++ b/doc/articles/race_detector.html
@@ -0,0 +1,388 @@
+<!--{
+	"Title": "Data Race Detector",
+	"Template": true
+}-->
+
+<h2 id="Introduction">Introduction</h2>
+
+<p>
+Data races are among the most common and hardest to debug types of bugs in concurrent systems.
+A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write.
+See the <a href="/ref/mem/">The Go Memory Model</a> for details.
+</p>
+
+<p>
+Here is an example of a data race that can lead to crashes and memory corruption:
+</p>
+
+<pre>
+func main() {
+	c := make(chan bool)
+	m := make(map[string]string)
+	go func() {
+		m["1"] = "a" // First conflicting access.
+		c <- true
+	}()
+	m["2"] = "b" // Second conflicting access.
+	<-c
+	for k, v := range m {
+		fmt.Println(k, v)
+	}
+}
+</pre>
+
+<h2 id="Usage">Usage</h2>
+
+<p>
+To help diagnose such bugs, Go includes a built-in data race detector.
+To use it, add the <code>-race</code> flag to the go command:
+</p>
+
+<pre>
+$ go test -race mypkg    // to test the package
+$ go run -race mysrc.go  // to run the source file
+$ go build -race mycmd   // to build the command
+$ go install -race mypkg // to install the package
+</pre>
+
+<h2 id="Report_Format">Report Format</h2>
+
+<p>
+When the race detector finds a data race in the program, it prints a report.
+The report contains stack traces for conflicting accesses, as well as stacks where the involved goroutines were created.
+Here is an example:
+</p>
+
+<pre>
+WARNING: DATA RACE
+Read by goroutine 185:
+  net.(*pollServer).AddFD()
+      src/pkg/net/fd_unix.go:89 +0x398
+  net.(*pollServer).WaitWrite()
+      src/pkg/net/fd_unix.go:247 +0x45
+  net.(*netFD).Write()
+      src/pkg/net/fd_unix.go:540 +0x4d4
+  net.(*conn).Write()
+      src/pkg/net/net.go:129 +0x101
+  net.func·060()
+      src/pkg/net/timeout_test.go:603 +0xaf
+
+Previous write by goroutine 184:
+  net.setWriteDeadline()
+      src/pkg/net/sockopt_posix.go:135 +0xdf
+  net.setDeadline()
+      src/pkg/net/sockopt_posix.go:144 +0x9c
+  net.(*conn).SetDeadline()
+      src/pkg/net/net.go:161 +0xe3
+  net.func·061()
+      src/pkg/net/timeout_test.go:616 +0x3ed
+
+Goroutine 185 (running) created at:
+  net.func·061()
+      src/pkg/net/timeout_test.go:609 +0x288
+
+Goroutine 184 (running) created at:
+  net.TestProlongTimeout()
+      src/pkg/net/timeout_test.go:618 +0x298
+  testing.tRunner()
+      src/pkg/testing/testing.go:301 +0xe8
+</pre>
+
+<h2 id="Options">Options</h2>
+
+<p>
+The <code>GORACE</code> environment variable sets race detector options.
+The format is:
+</p>
+
+<pre>
+GORACE="option1=val1 option2=val2"
+</pre>
+
+<p>
+The options are:
+</p>
+
+<ul>
+<li>
+<code>log_path</code> (default <code>stderr</code>): The race detector writes
+its report to a file named <code>log_path.<em>pid</em></code>.
+The special names <code>stdout</code>
+and <code>stderr</code> cause reports to be written to standard output and
+standard error, respectively.
+</li>
+
+<li>
+<code>exitcode</code> (default <code>66</code>): The exit status to use when
+exiting after a detected race.
+</li>
+
+<li>
+<code>strip_path_prefix</code> (default <code>""</code>): Strip this prefix
+from all reported file paths, to make reports more concise.
+</li>
+
+<li>
+<code>history_size</code> (default <code>1</code>): The per-goroutine memory
+access history is <code>32K * 2**history_size elements</code>.
+Increasing this value can avoid a "failed to restore the stack" error in reports, at the
+cost of increased memory usage.
+</li>
+
+<li>
+<code>halt_on_error</code> (default <code>0</code>): Controls whether the program
+exits after reporting first data race.
+</li>
+</ul>
+
+<p>
+Example:
+</p>
+
+<pre>
+$ GORACE="log_path=/tmp/race/report strip_path_prefix=/my/go/sources/" go test -race
+</pre>
+
+<h2 id="Excluding_Tests">Excluding Tests</h2>
+
+<p>
+When you build with <code>-race</code> flag, the <code>go</code> command defines additional
+<a href="/pkg/go/build/#hdr-Build_Constraints">build tag</a> <code>race</code>.
+You can use the tag to exclude some code and tests when running the race detector.
+Some examples:
+</p>
+
+<pre>
+// +build !race
+
+package foo
+
+// The test contains a data race. See issue 123.
+func TestFoo(t *testing.T) {
+	// ...
+}
+
+// The test fails under the race detector due to timeouts.
+func TestBar(t *testing.T) {
+	// ...
+}
+
+// The test takes too long under the race detector.
+func TestBaz(t *testing.T) {
+	// ...
+}
+</pre>
+
+<h2 id="How_To_Use">How To Use</h2>
+
+<p>
+To start, run your tests using the race detector (<code>go test -race</code>).
+The race detector only finds races that happen at runtime, so it can't find
+races in code paths that are not executed.
+If your tests have incomplete coverage,
+you may find more races by running a binary built with <code>-race</code> under a realistic
+workload.
+</p>
+
+<h2 id="Typical_Data_Races">Typical Data Races</h2>
+
+<p>
+Here are some typical data races.  All of them can be detected with the race detector.
+</p>
+
+<h3 id="Race_on_loop_counter">Race on loop counter</h3>
+
+<pre>
+func main() {
+	var wg sync.WaitGroup
+	wg.Add(5)
+	for i := 0; i < 5; i++ {
+		go func() {
+			fmt.Println(i) // Not the 'i' you are looking for.
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+}
+</pre>
+
+<p>
+The variable <code>i</code> in the function literal is the same variable used by the loop, so
+the read in the goroutine races with the loop increment.
+(This program typically prints 55555, not 01234.)
+The program can be fixed by making a copy of the variable:
+</p>
+
+<pre>
+func main() {
+	var wg sync.WaitGroup
+	wg.Add(5)
+	for i := 0; i < 5; i++ {
+		go func(j int) {
+			fmt.Println(j) // Good. Read local copy of the loop counter.
+			wg.Done()
+		}(i)
+	}
+	wg.Wait()
+}
+</pre>
+
+<h3 id="Accidentally_shared_variable">Accidentally shared variable</h3>
+
+<pre>
+// ParallelWrite writes data to file1 and file2, returns the errors.
+func ParallelWrite(data []byte) chan error {
+	res := make(chan error, 2)
+	f1, err := os.Create("file1")
+	if err != nil {
+		res <- err
+	} else {
+		go func() {
+			// This err is shared with the main goroutine,
+			// so the write races with the write below.
+			_, err = f1.Write(data)
+			res <- err
+			f1.Close()
+		}()
+	}
+	f2, err := os.Create("file2") // The second conflicting write to err.
+	if err != nil {
+		res <- err
+	} else {
+		go func() {
+			_, err = f2.Write(data)
+			res <- err
+			f2.Close()
+		}()
+	}
+	return res
+}
+</pre>
+
+<p>
+The fix is to introduce new variables in the goroutines (note the use of <code>:=</code>):
+</p>
+
+<pre>
+			...
+			_, err := f1.Write(data)
+			...
+			_, err := f2.Write(data)
+			...
+</pre>
+
+<h3 id="Unprotected_global_variable">Unprotected global variable</h3>
+
+<p>
+If the following code is called from several goroutines, it leads to races on the <code>service</code> map.
+Concurrent reads and writes of the same map are not safe:
+</p>
+
+<pre>
+var service map[string]net.Addr
+
+func RegisterService(name string, addr net.Addr) {
+	service[name] = addr
+}
+
+func LookupService(name string) net.Addr {
+	return service[name]
+}
+</pre>
+
+<p>
+To make the code safe, protect the accesses with a mutex:
+</p>
+
+<pre>
+var (
+	service   map[string]net.Addr
+	serviceMu sync.Mutex
+)
+
+func RegisterService(name string, addr net.Addr) {
+	serviceMu.Lock()
+	defer serviceMu.Unlock()
+	service[name] = addr
+}
+
+func LookupService(name string) net.Addr {
+	serviceMu.Lock()
+	defer serviceMu.Unlock()
+	return service[name]
+}
+</pre>
+
+<h3 id="Primitive_unprotected_variable">Primitive unprotected variable</h3>
+
+<p>
+Data races can happen on variables of primitive types as well (<code>bool</code>, <code>int</code>, <code>int64</code>, etc.),
+as in this example:
+</p>
+
+<pre>
+type Watchdog struct{ last int64 }
+
+func (w *Watchdog) KeepAlive() {
+	w.last = time.Now().UnixNano() // First conflicting access.
+}
+
+func (w *Watchdog) Start() {
+	go func() {
+		for {
+			time.Sleep(time.Second)
+			// Second conflicting access.
+			if w.last < time.Now().Add(-10*time.Second).UnixNano() {
+				fmt.Println("No keepalives for 10 seconds. Dying.")
+				os.Exit(1)
+			}
+		}
+	}()
+}
+</pre>
+
+<p>
+Even such "innocent" data races can lead to hard-to-debug problems caused by
+non-atomicity of the memory accesses,
+interference with compiler optimizations,
+or reordering issues accessing processor memory .
+</p>
+
+<p>
+A typical fix for this race is to use a channel or a mutex.
+To preserve the lock-free behavior, one can also use the
+<a href="/pkg/sync/atomic/"><code>sync/atomic</code></a> package.
+</p>
+
+<pre>
+type Watchdog struct{ last int64 }
+
+func (w *Watchdog) KeepAlive() {
+	atomic.StoreInt64(&amp;w.last, time.Now().UnixNano())
+}
+
+func (w *Watchdog) Start() {
+	go func() {
+		for {
+			time.Sleep(time.Second)
+			if atomic.LoadInt64(&amp;w.last) < time.Now().Add(-10*time.Second).UnixNano() {
+				fmt.Println("No keepalives for 10 seconds. Dying.")
+				os.Exit(1)
+			}
+		}
+	}()
+}
+</pre>
+
+<h2 id="Supported_Systems">Supported Systems</h2>
+
+<p>
+The race detector runs on <code>darwin/amd64</code>, <code>linux/amd64</code>, and <code>windows/amd64</code>.
+</p>
+
+<h2 id="Runtime_Overheads">Runtime Overhead</h2>
+
+<p>
+The cost of race detection varies by program, but for a typical program, memory
+usage may increase by 5-10x and execution time by 2-20x.
+</p>
diff --git a/doc/docs.html b/doc/docs.html
index 7aad8dadf4..212f345a22 100644
--- a/doc/docs.html
+++ b/doc/docs.html
@@ -143,7 +143,8 @@ Guided tours of Go programs.
 <li><a href="/doc/gdb">Debugging Go Code with GDB</a></li>
 <li><a href="/blog/godoc-documenting-go-code">Godoc: documenting Go code</a> - writing good documentation for <a href="/cmd/godoc/">godoc</a>.</li>
 <li><a href="/blog/profiling-go-programs">Profiling Go Programs</a></li>
-<li><a href="/blog/race-detector">Data Race Detector</a> - testing Go programs for race conditions.</li>
+<li><a href="/doc/articles/race_detector.html">Data Race Detector</a> - a manual for the data race detector.</li>
+<li><a href="/blog/race-detector">Introducing the Go Race Detector</a> - an introduction to the race detector.
 </ul>

コアとなるコードの解説

このコミットは、主に2つのファイルに対する変更を含んでいます。

  1. doc/articles/race_detector.html の新規追加: このファイルは、Goのデータ競合検出器に関する包括的なマニュアルのHTMLコンテンツです。コミットの差分を見ると、このファイルが完全に新規追加されていることがわかります(index 0000000000..282db8ba40 および --- /dev/null から始まる差分)。 ファイルの内容は、データ競合の定義、検出器の使用方法(go test -race など)、検出レポートの読み方、GORACE 環境変数によるオプション設定、ビルドタグを用いたテストの除外、そして具体的なデータ競合のパターンとその修正例(ループカウンタの競合、共有変数の誤用、グローバル変数の保護、プリミティブ型の競合など)を詳細に説明しています。また、サポートされるシステムと実行時のオーバーヘッドについても言及しています。 このマニュアルは、Go開発者がデータ競合を理解し、効果的にデバッグするための重要なリソースとなります。

  2. doc/docs.html の変更: このファイルは、Goの公式ドキュメントのインデックスページであり、様々なドキュメントやブログ記事へのリンクがリストされています。 変更点としては、データ競合検出器に関する既存のリンクが修正されています。 - <li><a href="/blog/race-detector">Data Race Detector</a> - testing Go programs for race conditions.</li> これは、以前のブログ記事へのリンクです。 + <li><a href="/doc/articles/race_detector.html">Data Race Detector</a> - a manual for the data race detector.</li> + <li><a href="/blog/race-detector">Introducing the Go Race Detector</a> - an introduction to the race detector.</li> 新しい変更では、ブログ記事へのリンクを残しつつ、新たに復元された詳細なマニュアル (/doc/articles/race_detector.html) へのリンクが追加されています。これにより、ユーザーはデータ競合検出器の概要を知るためのブログ記事と、より詳細な技術情報を提供するマニュアルの両方にアクセスできるようになりました。これは、ドキュメントの構造を改善し、ユーザーが求める情報にたどり着きやすくするための変更と言えます。

これらの変更により、Goのデータ競合検出器に関するドキュメントが整備され、開発者が並行処理のバグを効率的に特定・修正するための情報が提供されるようになりました。

関連リンク

参考にした情報源リンク