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

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

このコミットは、Go言語のランタイムにおける並列処理テスト(ParFor)が、Goのデータ競合検出器(Race Detector)と組み合わせて実行された際に発生する誤検知(false positive)の問題を解決するためのものです。具体的には、ParForの同期メカニズムがC言語で実装されているため、GoのRace Detectorがその内部の同期を正しく理解できず、実際には競合が発生していないにもかかわらず警告を発してしまう問題を修正しています。このコミットでは、Race Detectorが有効なビルド環境下ではParForのテストが実行されないように、ビルド制約(build tag)を追加することで対応しています。

コミット

  • Author: Dmitriy Vyukov dvyukov@google.com
  • Date: Tue Nov 6 12:09:40 2012 +0400
  • Commit Hash: d6b9a03b7f6f158c470405e2e4d6a68dcd094a95

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

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

元コミット内容

runtime: disable parallel for tests under race detector.
The race detector does not understand ParFor synchronization, because it's implemented in C.
If run with -cpu=2 currently race detector says:
 WARNING: DATA RACE
 Read by goroutine 5:
  runtime_test.TestParForParallel()
      src/pkg/runtime/parfor_test.go:118 +0x2e0
  testing.tRunner()
      src/pkg/testing/testing.go:301 +0x8f
 Previous write by goroutine 6:
  runtime_test.func·024()
      src/pkg/runtime/parfor_test.go:111 +0x52

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

変更の背景

Go言語のランタイムには、並列処理を効率的に行うための内部メカニズムとしてParFor(Parallel For)が存在します。これは、特定のループ処理を複数のゴルーチンに分散して実行することで、パフォーマンスを向上させることを目的としています。

一方で、Goにはプログラム実行中のデータ競合(data race)を検出するための強力なツールである「Race Detector」があります。データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みであり、かつそれらのアクセスが同期メカニズムによって保護されていない場合に発生します。これは並行プログラミングにおける一般的なバグの原因であり、予測不能な動作やクラッシュを引き起こす可能性があります。

このコミットが作成された当時、ParForの同期メカニズムの一部がC言語で実装されていました。GoのRace Detectorは、Go言語で書かれたコードのデータ競合を検出するように設計されていますが、C言語で実装された低レベルの同期プリミティブについては、その内部動作を完全に理解することができませんでした。

その結果、ParForを使用するテスト(特にruntime_test.TestParForParallel()のような並列テスト)をRace Detectorが有効な状態で実行すると、実際にはParFor内部で適切に同期が取られているにもかかわらず、Race Detectorがそれを認識できず、誤ってデータ競合の警告(WARNING: DATA RACE)を出力してしまう問題が発生していました。この誤検知は、開発者が真のデータ競合を見落とす原因となったり、不必要なデバッグ作業を発生させたりするため、開発体験を損なうものでした。

このコミットは、このような誤検知を回避し、Race Detectorの出力をより信頼性の高いものにするために導入されました。

前提知識の解説

Go Race Detector

Go Race Detectorは、Go 1.1で導入された動的解析ツールで、Goプログラム実行中に発生するデータ競合を検出します。データ競合は、並行処理において最も厄介なバグの一つであり、プログラムの非決定的な動作やクラッシュを引き起こす可能性があります。Race Detectorは、プログラムの実行パスを監視し、共有メモリへのアクセスパターンを追跡することで、同期されていないアクセスを特定します。

Race Detectorは、Goプログラムをビルドする際に-raceフラグを付けてコンパイルすることで有効になります(例: go build -racego test -race)。有効にすると、実行時にメモリへのアクセスを監視し、競合が検出された場合には、競合が発生した場所、関連するゴルーチン、およびスタックトレースを含む詳細な警告メッセージを出力します。

Race Detectorは非常に強力ですが、その検出はGoランタイムが認識できる同期プリミティブ(ミューテックス、チャネルなど)に依存しています。C言語で実装された低レベルの同期メカニズムは、Goランタイムの監視対象外となる場合があり、これが本コミットの背景にある問題を引き起こしました。

ParFor (Parallel For)

ParForは、Goランタイムの内部で使用される並列処理の抽象化です。これは、特定のループ処理を複数のゴルーチンに分割し、並列に実行することで、マルチコアプロセッサの恩恵を最大限に受けることを目的としています。ParForは、ユーザーが直接呼び出すAPIではなく、Goランタイムの内部でスケジューラやガベージコレクタなどのコンポーネントが、計算負荷の高いタスクを並列化するために利用します。

ParForは、その性質上、共有データへのアクセスを伴うため、内部で厳密な同期メカニズムを必要とします。この同期メカニズムの一部が、パフォーマンス上の理由や既存のCコードベースとの統合のために、C言語で実装されていました。

+build タグ (Build Constraints)

Go言語では、ソースファイルに特別なコメント行を追加することで、そのファイルを特定のビルド条件でのみコンパイルするように指定できます。これを「ビルド制約(build constraint)」または「+buildタグ」と呼びます。

+buildタグは、ファイルの先頭付近に// +build tag1 tag2のような形式で記述されます。複数のタグをスペースで区切って指定でき、論理AND(同じ行に複数のタグ)や論理OR(異なる行に複数のタグ)の条件を表現できます。

本コミットで使用されている// +build !raceは、非常に一般的なビルド制約の一つです。これは、「raceタグが有効でない場合にのみ、このファイルをビルドする」という意味を持ちます。つまり、go build -racego test -raceのようにRace Detectorが有効な状態でビルドされる際には、このタグを持つファイルはコンパイル対象から除外されます。これにより、特定のコードがRace Detectorの実行時にのみ含まれたり、除外されたりする挙動を制御できます。

技術的詳細

この問題の核心は、GoのRace DetectorがGo言語のメモリモデルと同期プリミティブに基づいて設計されている点にあります。Race Detectorは、Goのランタイムが提供する同期イベント(チャネル操作、ミューテックスロック/アンロックなど)をフックして、共有メモリへのアクセスが適切に保護されているかを判断します。

しかし、ParForの内部同期がC言語で実装されている場合、これらのC言語レベルの同期操作はGoランタイムのRace Detectorが直接監視できる範囲外となります。Race Detectorは、C言語のコードが実行する低レベルのアトミック操作やメモリバリア、あるいはOSレベルの同期プリミティブ(セマフォ、ミューテックスなど)を、Goの同期イベントとして認識できません。

その結果、ParForが並列に共有データにアクセスする際、C言語レベルで同期が取られていても、Race Detectorはそれを「同期されていないアクセス」と誤解し、データ競合の警告を発してしまいます。これは、Race Detectorが「Goのメモリモデルに準拠した同期」のみを理解し、それ以外の同期メカニズムを認識できないために生じる「誤検知」です。

この誤検知は、Race Detectorの信頼性を低下させ、開発者が本当に修正すべきデータ競合を見つけるのを困難にします。そのため、開発チームは、Race Detectorが正しく動作しない特定のテストケースを、Race Detectorが有効なビルドから除外するというアプローチを選択しました。

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

変更はsrc/pkg/runtime/parfor_test.goファイルに対して行われました。

--- a/src/pkg/runtime/parfor_test.go
+++ b/src/pkg/runtime/parfor_test.go
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// The race detector does not understand ParFor synchronization.
+// +build !race
+
 package runtime_test
 
 import (

具体的には、ファイルの先頭に以下の3行が追加されました。

// The race detector does not understand ParFor synchronization.
// +build !race

コアとなるコードの解説

追加された// +build !raceという行は、Goのビルド制約(build constraint)です。

  • // +build は、Goコンパイラに対する指示であり、この行がビルド制約であることを示します。
  • !race は、「raceタグが有効でない場合」という条件を表します。!は否定を意味します。

このビルド制約がsrc/pkg/runtime/parfor_test.goファイルの先頭に追加されたことにより、以下の挙動が実現されます。

  1. 通常ビルド時(Race Detector無効時): go buildgo testのように-raceフラグなしでコンパイルされる場合、raceタグは有効ではありません。したがって、!raceの条件が満たされ、parfor_test.goファイルは通常通りコンパイルされ、テストが実行されます。
  2. Race Detector有効ビルド時: go build -racego test -raceのように-raceフラグを付けてコンパイルされる場合、raceタグが有効になります。このとき、!raceの条件は満たされません。結果として、Goコンパイラはparfor_test.goファイルをコンパイル対象から除外します。

これにより、Race Detectorが有効な環境でGoのテストスイートが実行される際、ParForに関連するテスト(特に誤検知を引き起こす可能性のあるテスト)はスキップされるようになります。これは、Race Detectorの誤検知を回避し、テスト結果のノイズを減らすための実用的な解決策です。

コメント行// The race detector does not understand ParFor synchronization.は、このビルド制約が追加された理由を明確に説明しており、コードの可読性と保守性を高めています。

関連リンク

参考にした情報源リンク