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

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

このコミットは、Goランタイムのヘッダーファイルである src/pkg/runtime/runtime.h に変更を加えています。このファイルは、Goランタイムの内部構造、特にゴルーチン (Goroutine) やスケジューラ、メモリ管理に関連する重要なデータ構造の定義を含んでいます。C言語で記述されており、Goランタイムの低レベルな部分の実装に利用されます。

コミット

このコミットは、Goランタイムのコードフォーマットを修正するものです。主な目的は、ゴルーチンプリエンプション(goroutine preemption)を含む別のビルドを強制することにあります。

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

https://github.com/golang/go/commit/3d6bce411c001bd7665a052045a376ba1cf30b63

元コミット内容

runtime: fix code formatting
This is mainly to force another build
with goroutine preemption.

R=rsc
CC=golang-dev
https://golang.org/cl/12006045

変更の背景

このコミットの直接的な目的は「コードフォーマットの修正」とされていますが、コミットメッセージの2行目にある「This is mainly to force another build with goroutine preemption.(これは主に、ゴルーチンプリエンプションを含む別のビルドを強制するためである。)」が本質的な意図を示しています。

2013年当時、Go 1.2のリリースに向けて、Goランタイムにはゴルーチンプリエンプションの機能が導入されようとしていました。それ以前のGoのスケジューラは完全に協調的(cooperative)であり、ゴルーチンは明示的に runtime.Gosched() を呼び出すか、I/O操作を実行するか、チャネルやミューテックスでブロックしない限り、CPUの制御を解放しませんでした。これにより、長時間実行されるゴルーチンがCPUリソースを独占し、他のゴルーチンが飢餓状態に陥る可能性がありました。

Go 1.2で導入されたプリエンプションは、厳密には「コンパイラが組み込んだ協調的プリエンプション」でした。これは、関数のプロローグ(関数の開始部分)にプリエンプションチェックを挿入することで機能しました。もしゴルーチンが10ミリ秒以上実行されている場合、ランタイムはそれをプリエンプト可能としてマークします。次にこのゴルーチンが関数に入ると、コンパイラが挿入したチェックがプリエンプション要求を検出し、スケジューラが別のゴルーチンに切り替えることを可能にします。

このコミットは、コードの機能的な変更ではなく、わずかなフォーマット変更(ポインタの * の位置の調整)を行うことで、Goのビルドシステムをトリガーし、ゴルーチンプリエンプションの変更が組み込まれた新しいランタイムのビルドとテストを強制することを目的としています。これは、重要な機能変更が正しくビルドされ、テストパイプラインを通過することを確認するための一般的な手法です。

前提知識の解説

  • Goランタイム (Go Runtime): Goプログラムの実行を管理するシステムです。これには、ゴルーチン、スケジューラ、ガベージコレクタ、メモリ管理などが含まれます。Goプログラムは、オペレーティングシステム上で直接実行されるのではなく、Goランタイム上で実行されます。
  • ゴルーチン (Goroutine): Goにおける軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。GoランタイムのスケジューラがゴルーチンをOSスレッドにマッピングし、実行を管理します。
  • スケジューリング (Scheduling): 複数の実行可能なタスク(この場合はゴルーチン)がある場合に、どのタスクをいつ、どのCPUコアで実行するかを決定するプロセスです。
  • 協調的スケジューリング (Cooperative Scheduling): タスク自身が明示的にCPUの制御を解放する(yieldする)まで、そのタスクが実行され続ける方式です。タスクが自発的に制御を解放しない限り、他のタスクは実行機会を得られません。
  • プリエンプティブスケジューリング (Preemptive Scheduling): スケジューラが、実行中のタスクの意思に関わらず、強制的にCPUの制御を奪い、別のタスクに切り替えることができる方式です。これにより、長時間実行されるタスクがシステムを独占するのを防ぎ、公平なリソース配分を実現します。Go 1.2で導入されたのは、完全なプリエンプションではなく、関数の呼び出し時にチェックを行う「協調的プリエンプション」の一種でした。真の非協調的(非同期)プリエンプションはGo 1.14で導入されました。
  • src/pkg/runtime/runtime.h: GoランタイムのC言語部分で使われるヘッダーファイルです。Goランタイムの内部データ構造(例えば、G (Goroutine) や M (Machine/OSスレッド) 構造体など)の定義が含まれています。これらの構造体は、スケジューラがゴルーチンやOSスレッドの状態を管理するために使用します。

技術的詳細

このコミットの技術的な変更は非常に小さいですが、その背景にある意図は重要です。

変更内容は、src/pkg/runtime/runtime.h 内の構造体定義におけるポインタの宣言スタイルを修正するものです。具体的には、DeferChunk *dchunk; のような形式を DeferChunk* dchunk; のように、アスタリスク (*) を型名に近づけるスタイルに変更しています。同様に、MCache *mcache;int8* notesig; も修正されています。

これは、Goのコードベース全体で一貫したコーディングスタイルを維持するための一環であると考えられます。しかし、このコミットの真の目的は、このわずかな変更によってGoのビルドシステムをトリガーし、当時開発中であったゴルーチンプリエンプションの変更が正しくビルドパイプラインを通過するかを確認することでした。

Goのビルドシステムは、コードの変更を検知すると、関連するパッケージやランタイムを再ビルドします。この小さなフォーマット変更は、ランタイムの再ビルドを強制するのに十分であり、それによってゴルーチンプリエンプションの新しいロジックがコンパイルされ、テストされる機会が生まれます。これは、大規模なプロジェクトにおいて、特定の機能が正しく統合されていることを確認するための「トリガーコミット」として機能することがあります。

当時のGo 1.2のプリエンプションは、コンパイラが関数のプロローグにプリエンプションチェックを挿入する方式でした。このため、ランタイムのコード変更だけでなく、コンパイラ側の変更も必要でした。このコミットは、ランタイム側の変更がコンパイラ側の変更と連携して正しく機能するかを検証するビルドを促す役割を果たした可能性があります。

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

--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -277,8 +277,8 @@ struct	G
 	int32	sig;
 	int32	writenbuf;
 	byte*	writebuf;
-	DeferChunk	*dchunk;
-	DeferChunk	*dchunknext;
+	DeferChunk*	dchunk;
+	DeferChunk*	dchunknext;
 	uintptr	sigcode0;
 	uintptr	sigcode1;
 	uintptr	sigpc;
@@ -323,7 +323,7 @@ struct	M
 	M*	alllink;	// on allm
 	M*	schedlink;
 	uint32	machport;	// Return address for Mach IPC (OS X)
-	MCache	*mcache;
+	MCache*	mcache;
 	int32	stackinuse;
 	uint32	stackcachepos;
 	uint32	stackcachecnt;
@@ -353,7 +353,7 @@ struct	M
 	WinCall	wincall;
 #endif
 #ifdef GOOS_plan9
-	int8*		notesig;
+	int8*	notesig;
 	byte*	errstr;
 #endif
 	SEH*	seh;

コアとなるコードの解説

変更された箇所は、src/pkg/runtime/runtime.h 内の以下の3つの構造体メンバーの宣言です。

  1. struct G (Goroutine構造体):

    -	DeferChunk	*dchunk;
    -	DeferChunk	*dchunknext;
    +	DeferChunk*	dchunk;
    +	DeferChunk*	dchunknext;
    

    dchunkdchunknext は、Goの defer ステートメントの実装に関連するポインタです。defer は関数の終了時に実行される処理を登録するために使用されます。これらのポインタは、遅延実行される関数の情報を保持する DeferChunk 構造体へのポインタです。変更は、DeferChunk* の間のスペースを削除し、* を型名に密着させるスタイルに統一しています。

  2. struct M (Machine/OSスレッド構造体):

    -	MCache	*mcache;
    +	MCache*	mcache;
    

    mcache は、Goランタイムのメモリ管理におけるMキャッシュ(per-M cache)へのポインタです。Mキャッシュは、OSスレッドごとに割り当てられる小さなオブジェクトのキャッシュで、アロケーションの高速化に寄与します。ここでも、MCache* の間のスペースが削除されています。

  3. struct M (GOOS_plan9 向け):

    #ifdef GOOS_plan9
    -	int8*		notesig;
    +	int8*	notesig;
    

    notesig は、Plan 9オペレーティングシステム向けのビルドでのみ使用されるポインタです。Plan 9のシグナル処理に関連するものです。ここでも、int8* の間のスペースが調整されています。

これらの変更は、コードの機能には一切影響を与えません。C言語では、int *p;int* p; はどちらも同じ意味で、pint 型へのポインタであることを宣言します。このコミットは純粋にコーディングスタイルの一貫性を保つためのフォーマット変更であり、その副次的な効果として、Goのビルドシステムをトリガーし、当時開発中であったゴルーチンプリエンプションの変更を含む新しいランタイムのビルドを強制するという目的がありました。

関連リンク

参考にした情報源リンク