[インデックス 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つのアクセスが書き込みである: 競合するアクセスのうち、少なくとも1つがメモリの内容を変更する操作(書き込み)です。
- アクセスが同期されていない: アクセスがミューテックス、チャネル、アトミック操作などの同期プリミティブによって適切に保護されていないため、アクセス順序が保証されません。
データ競合が発生すると、プログラムの実行結果がアクセス順序に依存するようになり、非決定的な動作、データの破損、クラッシュなど、予測不能な結果を招きます。
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/race
がsyscall
に依存しないように設定されるか、syscall
がruntime
に依存しないように変更される)では、cmd/cgo
が-import_syscall=false
で実行され、syscall
パッケージへの不必要な依存関係が排除されます。これにより、runtime/race
とsyscall
間の循環依存関係が解消され、データ競合検出機能が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 {\
コアとなるコードの解説
-
src/cmd/cgo/main.go
の変更:var importSyscall = flag.Bool("import_syscall", true, "import syscall in generated code")
importSyscall
という新しいコマンドラインフラグが追加されました。これはブール型で、デフォルト値はtrue
です。- このフラグは、
cgo
が生成するGoコードにsyscall
パッケージをインポートするかどうかを制御します。
-
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/race
とsyscall
間の循環依存関係が回避され、Goのツールチェイン全体でデータ競合検出機能が適切に動作するようになります。
関連リンク
- 元の変更セット (CL): https://golang.org/cl/6456044
- このコミットの変更セット (CL): https://golang.org/cl/6498079
参考にした情報源リンク
- Go Data Race Detector: https://go.dev/blog/race-detector
- Go Packages: https://go.dev/doc/code#packages
- Go Cgo: https://go.dev/blog/cgo
- Go
syscall
package documentation: https://pkg.go.dev/syscall - Go
runtime/race
package documentation (if available, though it's internal): (General understanding from Go source code and discussions) - Go
cmd/cgo
source code: (General understanding from Go source code) - Go issue tracker and mailing lists for discussions on data race detection and circular dependencies. (General knowledge from Go community discussions)