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

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

このコミットは、Go言語の仕様書(doc/go_spec.html)に、パッケージの初期化が単一のゴルーチン内で順次実行されることを明示的に定義する変更を加えるものです。これにより、既存の実装の挙動が言語仕様として保証され、Goプログラムの予測可能性と堅牢性が向上します。

コミット

  • コミットハッシュ: b7ef3c9a5465762a19cec6bc925ddeda1a6b441a
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: 2011年10月27日 木曜日 12:22:45 -0700

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

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

元コミット内容

spec: define that initialization is sequential

This is true of the existing implementations, and I think
it is an important property to guarantee.

R=golang-dev, r, borman, r
CC=golang-dev
https://golang.org/cl/5321058

変更の背景

Go言語では、パッケージの初期化(グローバル変数の初期化とinit関数の実行)は、その設計上、特定の順序で行われることが期待されていました。しかし、この挙動が言語仕様として明示的に定義されていなかったため、将来の実装や異なる環境での挙動に不確実性が生じる可能性がありました。

このコミットの目的は、既存のGoコンパイラやランタイムの実装が既に採用している「初期化が順次行われる」という重要な特性を、正式な言語仕様に組み込むことです。これにより、開発者は初期化の順序に依存したコードを安心して記述できるようになり、Goプログラムの移植性と信頼性が高まります。Russ Cox氏(Go言語の主要な設計者の一人)は、この特性が保証されるべき重要なものであると考えていました。

前提知識の解説

Go言語のパッケージ初期化

Go言語のプログラムは、複数のパッケージで構成されます。プログラムが実行される際、これらのパッケージは特定の順序で初期化されます。初期化のプロセスには、以下の2つの主要なステップが含まれます。

  1. パッケージレベル変数の初期化: 各パッケージで宣言されたグローバル変数やパッケージレベルの変数が、その初期化式に基づいて評価され、値が割り当てられます。
  2. init関数の実行: 各パッケージは、initという特別な関数を複数定義することができます。これらのinit関数は、パッケージの初期化が完了した後に自動的に呼び出されます。init関数は引数を取らず、何も返しません。主に、プログラムの起動時に必要なセットアップ(データベース接続の確立、設定ファイルの読み込み、外部サービスの初期化など)を行うために使用されます。

Go言語の仕様では、パッケージの依存関係に基づいて初期化の順序が決定されます。あるパッケージが別のパッケージをインポートしている場合、インポートされる側のパッケージが先に初期化されます。循環インポートは許可されていません。

init関数

init関数は、Go言語における非常に強力な機能の一つです。

  • 自動実行: main関数が呼び出される前に、関連するパッケージのinit関数がすべて実行されます。
  • 複数定義: 1つのパッケージ内に複数のinit関数を定義できます。これらはソースファイル内での出現順に実行されます。
  • 用途:
    • プログラム起動時の設定ロード
    • データベース接続の初期化
    • 外部ライブラリのセットアップ
    • テスト環境の準備
    • 登録処理(例: 特定のインターフェースを実装する型を登録する)

ゴルーチン (Goroutines)

ゴルーチンは、Go言語における軽量な並行処理の単位です。スレッドに似ていますが、より軽量で、Goランタイムによって管理されます。

  • 軽量性: 数千、数万のゴルーチンを同時に実行しても、システムリソースへの負担は小さいです。
  • 並行性: goキーワードを使って関数呼び出しの前に置くことで、その関数を新しいゴルーチンとして実行し、他の処理と並行して実行させることができます。
  • チャネル: ゴルーチン間の安全な通信にはチャネルが使用されます。

このコミットの文脈では、init関数内でゴルーチンを起動できること、そしてそのゴルーチンが初期化コードと並行して実行されうるという点が重要です。しかし、init関数自体の実行順序は保証されるべきである、という点が強調されています。

Go言語仕様 (Go Language Specification)

Go言語仕様は、Go言語の構文、セマンティクス、および標準ライブラリの動作を正式に記述した文書です。Go言語のコンパイラやツール、ランタイムの実装は、この仕様に厳密に従う必要があります。仕様に明記されていない挙動は、実装依存となる可能性があり、異なる環境やGoのバージョン間で挙動が変わる可能性があります。そのため、重要な挙動は仕様に明記されることが望ましいとされます。

技術的詳細

このコミットは、Go言語仕様の「Program execution」セクションに、パッケージ初期化に関する重要な記述を追加します。追加された内容は以下の通りです。

Package initialization—variable initialization and the invocation of init functions—happens in a single goroutine, sequentially, one package at a time. An init function may launch other goroutines, which can run concurrently with the initialization code. However, initialization always sequences the init functions: it will not start the next init until the previous one has returned.

この記述は、以下の点を明確にしています。

  1. 単一ゴルーチンでの実行: パッケージの初期化(変数の初期化とinit関数の呼び出し)は、常に単一のゴルーチン内で実行されます。これは、初期化プロセス自体が並行して行われることはなく、順序が保証されることを意味します。
  2. 順次実行: 各パッケージの初期化は、一度に1つのパッケージずつ、順次行われます。これは、パッケージAの初期化が完了してからパッケージBの初期化が開始される、という明確な順序があることを示します。
  3. init関数の順次実行: 複数のinit関数がある場合、それらは常に順次実行されます。つまり、あるinit関数が完了するまで、次のinit関数は開始されません。
  4. init関数内でのゴルーチン起動: init関数自体は単一ゴルーチン内で順次実行されますが、init関数の中から新しいゴルーチンを起動することは可能です。そして、これらの新しく起動されたゴルーチンは、初期化コード(init関数)と並行して実行されます。これは、init関数が非同期処理を開始できることを意味しますが、init関数自体の完了は、そのinit関数が起動したゴルーチンの完了を待たないことを示唆しています。

この仕様の追加により、Goプログラムの初期化フェーズにおける並行性と順序に関する曖昧さが解消され、より予測可能で堅牢なプログラムの設計が可能になります。特に、init関数内でリソースのセットアップや外部サービスへの接続を行う際に、その処理が完了するまで次の初期化ステップが開始されないという保証は、多くのシナリオで重要となります。

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

変更は、Go言語仕様のHTMLドキュメントである doc/go_spec.html に対して行われました。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -5149,12 +5149,22 @@ func main() { … }\n <p>\n Program execution begins by initializing the main package and then\n invoking the function <code>main</code>.\n-\n When the function <code>main</code> returns, the program exits.\n It does not wait for other (non-<code>main</code>) goroutines to complete.\n </p>\n \n+\n+Package initialization&mdash;variable initialization and the invocation of \n+<code>init</code> functions&mdash;happens in a single goroutine,\n+sequentially, one package at a time.\n+An <code>init</code> function may launch other goroutines, which can run\n+concurrently with the initialization code. However, initialization\n+always sequences\n+the <code>init</code> functions: it will not start the next\n+<code>init</code> until\n+the previous one has returned.\n+\n <h2 id=\"Run_time_panics\">Run-time panics</h2>\n \n <p>\

具体的には、func main() { … }Run-time panics のセクションの間に、新しい段落が追加されています。

コアとなるコードの解説

追加されたHTMLコードは、Go言語仕様の「Program execution」セクションに、パッケージ初期化の挙動に関する新しいルールを導入しています。

<p>
Package initialization&mdash;variable initialization and the invocation of 
<code>init</code> functions&mdash;happens in a single goroutine,
sequentially, one package at a time.
An <code>init</code> function may launch other goroutines, which can run
concurrently with the initialization code. However, initialization
always sequences
the <code>init</code> functions: it will not start the next
<code>init</code> until
the previous one has returned.
</p>

この<p>タグで囲まれたテキストが、Go言語のパッケージ初期化の順序性と並行性に関する新しい公式な定義です。

  • Package initialization&mdash;variable initialization and the invocation of <code>init</code> functions&mdash;happens in a single goroutine, sequentially, one package at a time.
    • 「パッケージの初期化(変数の初期化とinit関数の呼び出し)は、単一のゴルーチン内で、一度に1つのパッケージずつ、順次行われる」ことを明確に述べています。これは、初期化プロセス全体が直列的に実行されることを保証します。
  • An <code>init</code> function may launch other goroutines, which can run concurrently with the initialization code.
    • init関数は他のゴルーチンを起動することができ、それらのゴルーチンは初期化コードと並行して実行されうる」ことを示しています。これは、init関数が非同期処理を開始できる柔軟性を持つことを認めつつ、その非同期処理がinit関数自体の完了をブロックしないことを示唆しています。
  • However, initialization always sequences the <code>init</code> functions: it will not start the next <code>init</code> until the previous one has returned.
    • 「しかし、初期化は常にinit関数を順次実行する。つまり、前のinit関数が戻るまで、次のinit関数は開始されない」という重要な保証をしています。これにより、同じパッケージ内や異なるパッケージ間のinit関数の実行順序が明確になり、依存関係のある初期化処理を安全に記述できるようになります。

この変更は、Go言語のランタイムの挙動をより厳密に定義し、開発者が初期化のタイミングと順序に依存するコードをより自信を持って書けるようにするための、重要な仕様の明確化と言えます。

関連リンク

  • Go Change List (CL): https://golang.org/cl/5321058
    • このコミットに対応するGoの変更リスト(コードレビューシステム)のページです。詳細な議論やレビューコメントが確認できます。

参考にした情報源リンク

(上記の参考情報源は、Go言語のパッケージ初期化、init関数、ゴルーチン、および言語仕様に関する一般的な知識を補完するために参照しました。)