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

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

このコミットは、Go言語の標準ライブラリであるtext/templateおよびhtml/templateパッケージにおいて、パースされたテンプレートが並行実行に対して安全であるという情報を、より明確かつ頻繁にドキュメントに追加することを目的としています。これにより、開発者がテンプレートを複数のGoroutineから安心して利用できることを保証し、特にWebアプリケーションのような並行処理が頻繁に行われる環境での誤解を解消します。

コミット

  • コミットハッシュ: aeb37527d3795b9677295bb21c0bbb3af18d6f31
  • 作者: Rob Pike r@golang.org
  • コミット日時: 2014年4月15日 火曜日 08:48:40 -0700

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

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

元コミット内容

text/template: say more often that templates are safe for parallel execution
It was said already but apparently not enough times.

Fixes #6985.

LGTM=crawshaw
R=golang-codereviews, crawshaw
CC=golang-codereviews
https://golang.org/cl/86300043

変更の背景

Go言語のtext/templateおよびhtml/templateパッケージは、Webアプリケーションなどで動的なコンテンツを生成する際に広く利用されています。これらのパッケージは、テンプレートをパース(解析)し、データと結合して最終的な出力を生成する機能を提供します。

このコミットが行われた背景には、Goの並行処理モデル(Goroutine)と、テンプレートの「スレッドセーフティ」(並行実行の安全性)に関する開発者の誤解や不明瞭さがありました。特に、net/httpパッケージのようなWebサーバーでは、各リクエストが独立したGoroutineで処理されるため、同じテンプレートインスタンスが複数のGoroutineから同時に利用されることが一般的です。

コミットメッセージにある「It was said already but apparently not enough times.」(既に言われていたことだが、どうやら十分ではなかったようだ)という記述は、テンプレートが並行実行に対して安全であるという情報が、既存のドキュメントに存在していたものの、その明確性や可視性が不足していたことを示唆しています。

Issue #6985(Fixes #6985)は、このドキュメントの不明瞭さを指摘するものでした。Goの慣習として、明示的にスレッドセーフであるとドキュメントされていない限り、そのモジュールはスレッドセーフではないと見なされる傾向があります。このため、テンプレートパッケージのドキュメントが不十分であると、開発者はテンプレートの並行利用を避けたり、不必要な同期メカニズムを導入したりする可能性がありました。

このコミットは、このような混乱を解消し、開発者がGoのテンプレートパッケージを自信を持って並行環境で利用できるように、ドキュメントを強化することを目的としています。

前提知識の解説

Goのtext/templatehtml/templateパッケージ

  • text/template: 任意のテキスト形式のテンプレートを扱うためのパッケージです。プレーンテキスト、設定ファイル、コード生成など、HTML以外の様々な用途で利用されます。
  • html/template: text/templateをベースにしており、HTMLコンテンツを安全に生成するために特化しています。クロスサイトスクリプティング(XSS)攻撃を防ぐために、自動的にHTMLエスケープ処理を行います。これにより、ユーザー入力などの信頼できないデータをテンプレートに埋め込む際に、セキュリティ上の脆弱性を低減します。

両パッケージともに、テンプレートの定義、パース、そしてデータとの結合による実行という基本的なワークフローは共通しています。

テンプレートのパースと実行

  1. パース(Parsing): テンプレート文字列(例: Hello, {{.Name}}!)を解析し、内部的なデータ構造(AST: 抽象構文木)に変換するプロセスです。この段階でテンプレートの構文チェックが行われます。
  2. 実行(Execution): パースされたテンプレートに、Goの構造体やマップなどのデータを適用し、最終的な出力(例: Hello, World!)を生成するプロセスです。

Goの並行処理(GoroutineとConcurrency)

Go言語は、軽量なスレッドである「Goroutine」と、それらを効率的にスケジューリングするランタイムによって、高い並行処理能力を提供します。

  • Goroutine: 数KB程度のスタックサイズで起動できる非常に軽量な実行単位です。数千、数万のGoroutineを同時に実行しても、システムリソースを効率的に利用できます。
  • Concurrency(並行性): 複数のタスクが同時に進行しているように見える状態を指します。Goでは、Goroutineとチャネル(Goroutine間の安全な通信メカニズム)を用いて並行処理を記述します。

スレッドセーフティ(Thread Safety)

スレッドセーフティとは、複数のスレッド(Goの場合はGoroutine)から同時にアクセスされた場合でも、プログラムが正しく動作し、データ競合(Data Race)や不正な状態に陥らないことを保証する性質です。

  • データ競合: 複数のGoroutineが同じメモリ領域に同時にアクセスし、少なくとも1つのアクセスが書き込みである場合に発生します。データ競合は予測不能な動作やバグの原因となります。
  • Goにおけるスレッドセーフティの慣習: Goでは、共有される可変データへのアクセスは、明示的な同期メカニズム(ミューテックス、チャネルなど)によって保護されるべきです。また、ライブラリやパッケージのドキュメントでは、その機能がスレッドセーフであるかどうかを明示的に記述することが推奨されます。もし記述がない場合、それはスレッドセーフではないと見なされるべきです。

このコミットは、テンプレートの「パース後の実行」がスレッドセーフであることを明確にすることで、開発者が安心してテンプレートを共有し、複数のGoroutineから同時に利用できるようにします。

技術的詳細

このコミットの主要な目的は、コードの振る舞いを変更することではなく、既存の振る舞い(パースされたテンプレートの並行実行の安全性)に関するドキュメントを強化することにあります。

具体的には、以下のファイルと箇所に、テンプレートが並行実行に対して安全である旨の記述が追加または修正されました。

  1. src/pkg/html/template/template.go:

    • Template.ExecuteメソッドとTemplate.ExecuteTemplateメソッドのドキュメントコメントに、「A template may be executed safely in parallel.」という文が追加されました。これは、html/templateパッケージの主要な実行メソッドが、並行環境下で安全に呼び出せることを明示しています。
    • html/templateは、HTMLエスケープ処理を行う際に内部状態を更新する可能性がありますが、過去のコミット(Issue 2439など)で既にこれらの操作がスレッドセーフになるように修正されています。今回のコミットは、その安全性をドキュメントレベルで再確認し、開発者に保証するものです。
  2. src/pkg/text/template/doc.go:

    • パッケージ全体のドキュメントを記述するファイルです。
    • 既存の「Once constructed, a template may be executed safely in parallel.」という記述が、「Once parsed, a template may be executed safely in parallel.」に修正されました。
    • この変更は、「constructed」(構築された)という曖昧な表現を「parsed」(パースされた)というより明確な表現に置き換えることで、テンプレートがメモリ上にロードされ、解析が完了した時点から並行実行に対して安全であることを強調しています。これにより、テンプレートオブジェクトが完全に準備された状態であれば、複数のGoroutineから同時にExecuteExecuteTemplateを呼び出しても問題ないことが明確になります。
  3. src/pkg/text/template/exec.go:

    • text/templateパッケージの実行関連のコードが含まれるファイルです。
    • Template.ExecuteメソッドとTemplate.ExecuteTemplateメソッドのドキュメントコメントに、「A template may be executed safely in parallel.」という文が追加されました。これは、html/templateと同様に、text/templateの主要な実行メソッドが並行環境下で安全であることを明示しています。

これらの変更は、Goのテンプレートパッケージが、設計上、パース後に不変(immutable)な状態となり、実行時に共有されるデータ構造を変更しないことを再確認するものです。これにより、複数のGoroutineが同じテンプレートインスタンスを同時に読み取り専用で利用しても、データ競合が発生しないことが保証されます。

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

diff --git a/src/pkg/html/template/template.go b/src/pkg/html/template/template.go
index 11cc34a50a..744f139ba4 100644
--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -62,6 +62,7 @@ func (t *Template) escape() error {
 
 // Execute applies a parsed template to the specified data object,
 // writing the output to wr.
+// A template may be executed safely in parallel.
 func (t *Template) Execute(wr io.Writer, data interface{}) error {
  	if err := t.escape(); err != nil {
  		return err
@@ -71,6 +72,7 @@ func (t *Template) Execute(wr io.Writer, data interface{}) error {
 
 // ExecuteTemplate applies the template associated with t that has the given
 // name to the specified data object and writes the output to wr.
+// A template may be executed safely in parallel.
 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
  	tmpl, err := t.lookupAndEscapeTemplate(name)
  	if err != nil {
diff --git a/src/pkg/text/template/doc.go b/src/pkg/text/template/doc.go
index f622ac7dce..7c6efd59cd 100644
--- a/src/pkg/text/template/doc.go
+++ b/src/pkg/text/template/doc.go
@@ -20,7 +20,7 @@ The input text for a template is UTF-8-encoded text in any format.
 "}}" and "}}"; all text outside actions is copied to the output unchanged.
 Actions may not span newlines, although comments can.
 
-Once constructed, a template may be executed safely in parallel.
+Once parsed, a template may be executed safely in parallel.
 
 Here is a trivial example that prints "17 items are made of wool".
 
diff --git a/src/pkg/text/template/exec.go b/src/pkg/text/template/exec.go
index 6de37a1996..505509a085 100644
--- a/src/pkg/text/template/exec.go
+++ b/src/pkg/text/template/exec.go
@@ -108,6 +108,7 @@ func errRecover(errp *error) {
 
 // ExecuteTemplate applies the template associated with t that has the given name
 // to the specified data object and writes the output to wr.
+// A template may be executed safely in parallel.
 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
  	tmpl := t.tmpl[name]
  	if tmpl == nil {
@@ -118,6 +119,7 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})\
 
 // Execute applies a parsed template to the specified data object,
 // and writes the output to wr.
+// A template may be executed safely in parallel.
 func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
  	defer errRecover(&err)
  	value := reflect.ValueOf(data)

コアとなるコードの解説

上記の差分は、Goのテンプレートパッケージにおけるドキュメントの変更を示しています。

  1. src/pkg/html/template/template.go の変更:

    • Template型のExecuteメソッドとExecuteTemplateメソッドのコメントブロックに、新たに「A template may be executed safely in parallel.」という行が追加されました。
    • これは、html/templateパッケージのテンプレートインスタンスが、複数のGoroutineから同時にExecuteまたはExecuteTemplateを呼び出されても、データ競合や不正な状態に陥ることなく安全に動作することを明示的に保証するものです。これにより、Webサーバーなどで同じテンプレートを複数のリクエスト処理Goroutineで共有し、並行して利用する際の開発者の懸念が解消されます。
  2. src/pkg/text/template/doc.go の変更:

    • パッケージの概要を説明するドキュメント内で、「Once constructed, a template may be executed safely in parallel.」という記述が「Once parsed, a template may be executed safely in parallel.」に修正されました。
    • 「constructed」(構築された)という言葉は、テンプレートが初期化された時点を指すため、やや曖昧でした。これを「parsed」(パースされた)に置き換えることで、テンプレートが完全に解析され、実行可能な状態になった時点から並行実行に対して安全であるという意図がより明確になりました。これは、テンプレートのパース処理自体は並行ではないが、パースが完了した後の実行フェーズは並行安全であることを強調しています。
  3. src/pkg/text/template/exec.go の変更:

    • Template型のExecuteメソッドとExecuteTemplateメソッドのコメントブロックに、html/templateと同様に「A template may be executed safely in parallel.」という行が追加されました。
    • これにより、text/templateパッケージにおいても、パースされたテンプレートの実行が並行環境で安全であることが明確に示されました。

これらの変更は、Goのテンプレートパッケージが、一度パースされると内部状態が不変(immutable)となり、実行時に共有されるデータ構造を変更しないという設計原則を再確認するものです。この不変性により、複数のGoroutineが同じテンプレートインスタンスを同時に読み取り専用で利用しても、データ競合が発生する心配がありません。結果として、開発者はGoの並行処理モデルを最大限に活用し、テンプレートを効率的かつ安全に利用できるようになります。

関連リンク

参考にした情報源リンク