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

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

このコミットは、Go言語のツールチェインにおけるcmd/cgoに対する変更を記述しています。具体的には、データ競合検出機能の導入に関連するもので、runtime/raceパッケージとsyscallパッケージ間の循環依存関係を解消することを目的としています。

コミット

commit 99b6e9f73b2b6a9e4d415064a271bfe579b8b66c
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Sep 18 23:42:18 2012 +0400

    race: cmd/cgo changes
    This is a part of a bigger change that adds data race detection feature:
    https://golang.org/cl/6456044
    This change breaks circular dependency between runtime/race and syscall packages.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6498079

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

https://github.com/golang/go/commit/99b6e9f73b2b6a9e4d415064a271bfe579b8b66c

元コミット内容

このコミットは、データ競合検出機能を追加する大規模な変更の一部です。runtime/raceパッケージとsyscallパッケージ間の循環依存関係を解消することを目的としています。

変更の背景

Go言語の並行処理モデルはゴルーチンとチャネルによって強力な並行性を実現しますが、同時にデータ競合(data race)という問題を引き起こす可能性があります。データ競合は、複数のゴルーチンが同時に同じメモリ位置にアクセスし、少なくとも1つのアクセスが書き込みである場合に発生し、プログラムの予測不能な動作やクラッシュの原因となります。

このコミットが行われた2012年当時、Go言語はデータ競合検出機能の導入を進めていました。データ競合検出器は、実行時にメモリアクセスを監視し、データ競合のパターンを特定するツールです。この機能は、Goプログラムの信頼性とデバッグ可能性を大幅に向上させるために不可欠でした。

しかし、データ競合検出器の実装には、Goのランタイム(runtimeパッケージ)とシステムコール(syscallパッケージ)の間に特定の依存関係が生じることがありました。特に、runtime/raceパッケージがsyscallパッケージに依存し、かつsyscallパッケージが何らかの形でruntimeパッケージ(またはその一部)に依存している場合、循環依存関係が発生します。このような循環依存関係は、Goのパッケージシステムでは許容されず、コンパイルエラーやビルドの問題を引き起こします。

このコミットの主な目的は、この循環依存関係を解消し、データ競合検出機能の健全な統合を可能にすることでした。

前提知識の解説

データ競合 (Data Race)

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

  1. 複数のゴルーチンが同じメモリ位置にアクセスする: 複数のゴルーチンが同じ変数やデータ構造を読み書きしようとします。
  2. 少なくとも1つのアクセスが書き込みである: 競合するアクセスのうち、少なくとも1つがメモリの内容を変更する操作(書き込み)です。
  3. アクセスが同期されていない: アクセスがミューテックス、チャネル、アトミック操作などの同期プリミティブによって適切に保護されていないため、アクセス順序が保証されません。

データ競合が発生すると、プログラムの実行結果がアクセス順序に依存するようになり、非決定的な動作、データの破損、クラッシュなど、予測不能な結果を招きます。

Goのパッケージシステムと循環依存関係

Go言語のパッケージシステムは、依存関係を明確に定義し、コードの再利用性とモジュール性を高めるように設計されています。Goのビルドシステムは、パッケージ間の循環依存関係を厳しく禁止しています。これは、ビルドプロセスの複雑化を防ぎ、コードの理解と保守を容易にするためです。

例えば、パッケージAがパッケージBに依存し、同時にパッケージBがパッケージAに依存している場合、どちらのパッケージを先にコンパイルすべきかという問題が生じます。Goのビルドシステムはこのような状況を検出し、エラーとして報告します。

cmd/cgo

cmd/cgoは、GoプログラムからC言語のコードを呼び出すためのツールです。GoとCの間のインターフェースを生成し、Cの関数やデータ構造をGoから利用できるようにします。Cgoは、Goの標準ライブラリの多くの部分で、OSのシステムコールや外部ライブラリとの連携のために利用されています。

runtime/raceパッケージ

runtime/raceパッケージは、Goのデータ競合検出器の実装に関連するコードを含んでいます。このパッケージは、プログラムの実行中にメモリアクセスを監視し、データ競合のパターンを特定するための低レベルのフックや機能を提供します。

syscallパッケージ

syscallパッケージは、オペレーティングシステムのシステムコールへの低レベルなインターフェースを提供します。ファイルI/O、ネットワーク通信、プロセス管理など、OSの基本的な機能にアクセスするために使用されます。

技術的詳細

このコミットの技術的な核心は、cmd/cgoが生成するGoコードにおいて、syscallパッケージのインポートを条件付きにすることです。

以前のcmd/cgoは、生成されるGoコードに常にimport "syscall"func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }を含んでいました。_Cerrno関数は、CgoがCのerrno変数をGoのエラー型に変換するために使用するヘルパー関数です。

データ競合検出機能の導入に伴い、runtime/raceパッケージがsyscallパッケージに依存するようになりました。しかし、もしsyscallパッケージが何らかの理由でruntimeパッケージ(またはruntime/raceが依存するruntimeの特定の部分)に依存している場合、runtime/race -> syscall -> runtimeという循環依存関係が形成されてしまいます。

このコミットは、cmd/cgoに新しいフラグ-import_syscallを追加し、デフォルトでtrueに設定します。そして、out.go内のコード生成ロジックを変更し、このフラグがtrueの場合にのみsyscallパッケージのインポートと_Cerrno関数の定義を生成するようにしました。

これにより、データ競合検出器が有効なビルド(おそらくruntime/racesyscallに依存しないように設定されるか、syscallruntimeに依存しないように変更される)では、cmd/cgo-import_syscall=falseで実行され、syscallパッケージへの不必要な依存関係が排除されます。これにより、runtime/racesyscall間の循環依存関係が解消され、データ競合検出機能がGoのビルドシステムに適切に統合される道が開かれました。

具体的には、src/cmd/cgo/main.goで新しいブール型フラグimportSyscallが定義され、src/cmd/cgo/out.goでこのフラグの値に基づいてsyscallパッケージのインポートと_Cerrno関数の生成が条件付けられています。

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

src/cmd/cgo/main.go

--- a/src/cmd/cgo/main.go
+++ b/src/cmd/cgo/main.go
@@ -147,6 +147,7 @@ var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
 var gccgoprefix = flag.String("gccgoprefix", "", "-fgo-prefix option used with gccgo")
 var gccgopkgpath = flag.String("gccgopkgpath", "", "-fgo-pkgpath option used with gccgo")
 var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
+var importSyscall = flag.Bool("import_syscall", true, "import syscall in generated code")
 var goarch, goos string
 
 func main() {

src/cmd/cgo/out.go

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -51,12 +51,16 @@ func (p *Package) writeDefs() {
 	fmt.Fprintf(fgo2, "// Created by cgo - DO NOT EDIT\\n\\n")
 	fmt.Fprintf(fgo2, "package %s\\n\\n", p.PackageName)
 	fmt.Fprintf(fgo2, "import \\\"unsafe\\\"\\n\\n")
-\tfmt.Fprintf(fgo2, "import \\\"syscall\\\"\\n\\n")
+\tif *importSyscall {\n+\t\tfmt.Fprintf(fgo2, "import \\\"syscall\\\"\\n\\n")\n+\t}\n \tif !*gccgo && *importRuntimeCgo {\n \t\tfmt.Fprintf(fgo2, "import _ \\\"runtime/cgo\\\"\\n\\n")\n \t}\n \tfmt.Fprintf(fgo2, "type _ unsafe.Pointer\\n\\n")
-\tfmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }\\n")
+\tif *importSyscall {\n+\t\tfmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }\\n")\n+\t}\n 
 \ttypedefNames := make([]string, 0, len(typedef))\n \tfor name := range typedef {\

コアとなるコードの解説

  1. src/cmd/cgo/main.goの変更:

    • var importSyscall = flag.Bool("import_syscall", true, "import syscall in generated code")
      • importSyscallという新しいコマンドラインフラグが追加されました。これはブール型で、デフォルト値はtrueです。
      • このフラグは、cgoが生成するGoコードにsyscallパッケージをインポートするかどうかを制御します。
  2. src/cmd/cgo/out.goの変更:

    • fmt.Fprintf(fgo2, "import \"syscall\"\\n\\n") の行が、 if *importSyscall { fmt.Fprintf(fgo2, "import \"syscall\"\\n\\n") } に変更されました。
      • これにより、syscallパッケージのインポート文が、importSyscallフラグがtrueの場合にのみ生成されるようになりました。
    • fmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }\\n") の行が、 if *importSyscall { fmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }\\n") } に変更されました。
      • 同様に、_Cerrnoヘルパー関数の定義も、importSyscallフラグがtrueの場合にのみ生成されるようになりました。

これらの変更により、cgoは、データ競合検出器のビルドプロセスにおいて、syscallパッケージへの依存関係を動的に制御できるようになります。これにより、runtime/racesyscall間の循環依存関係が回避され、Goのツールチェイン全体でデータ競合検出機能が適切に動作するようになります。

関連リンク

参考にした情報源リンク