[インデックス 12647] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージ内の triv.go
ファイルに対する変更です。triv.go
は、net/http
パッケージの基本的な使用例や、expvar
パッケージとの連携を示すための、いわゆる「自明な(trivial)」なHTTPサーバーの例を含んでいます。具体的には、シンプルな「Hello World」サーバー、カウンタースサーバー、ロガー、ファイルサーバーなどの機能が実装されています。このファイルは、net/http
パッケージの機能をデモンストレーションするためのサンプルコード集としての役割を担っています。
コミット
このコミットは、net/http
パッケージの triv.go
ファイルに対して、いくつかの「現代化(modernizations)」を適用するものです。主な変更点は、カウンタースサーバーの並行処理安全性の確保(スレッドセーフ化)と、HTTPハンドラの登録方法の簡素化です。これにより、サンプルコードがより堅牢で、現代のGoの慣用的な書き方に沿ったものになっています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a4e6197b91e2bc16616245a3a704b89ab99db949
元コミット内容
net/http: couple more triv.go modernizations
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5834049
変更の背景
この変更の背景には、主に以下の2つの目的があります。
- カウンタースサーバーの並行処理安全性(スレッドセーフティ)の確保: 以前の
Counter
実装では、複数のHTTPリクエストが同時にカウンタの値を更新しようとした場合、競合状態(race condition)が発生し、正確なカウントが保証されない可能性がありました。これは、並行処理がGo言語の重要な特徴であるにもかかわらず、サンプルコードがそのベストプラクティスに従っていなかったことを意味します。このコミットは、sync.Mutex
を導入することで、カウンタの増減操作を排他的に保護し、並行アクセス時のデータ破損を防ぐことを目的としています。 - HTTPハンドラ登録の慣用的な書き方への移行:
http.Handle
とhttp.HandlerFunc
の組み合わせは有効ですが、Go 1.0以降ではhttp.HandleFunc
というより簡潔な関数が提供されています。これは、func(ResponseWriter, *Request)
型の関数を直接ハンドラとして登録できる糖衣構文(syntactic sugar)であり、コードの可読性と記述性を向上させます。この変更は、サンプルコードをGoの最新の慣用的な書き方に合わせることで、新規開発者にとってより良い学習リソースとなることを意図しています。 webroot
の初期値の改善:webroot
のデフォルト値がハードコードされたパス (/home/rsc
) から、ユーザーのホームディレクトリ (os.Getenv("HOME")
) に変更されました。これにより、サンプルコードがより汎用的に、異なる環境で動作するようになります。
これらの変更は、triv.go
が単なるサンプルコードではなく、Goのベストプラクティスと現代的なコーディングスタイルを示す模範となるようにするための継続的な改善の一環です。
前提知識の解説
Go言語の並行処理とsync.Mutex
Go言語は、ゴルーチン(goroutine)とチャネル(channel)を用いた並行処理を強力にサポートしています。しかし、複数のゴルーチンが共有データに同時にアクセスする場合、競合状態が発生し、予期せぬ結果やデータ破損を引き起こす可能性があります。これを防ぐために、Goの標準ライブラリには sync
パッケージが提供されており、その中に sync.Mutex
(ミューテックス)という排他制御の仕組みがあります。
- ミューテックス(Mutex): Mutual Exclusion(相互排他)の略で、複数のゴルーチンが同時に共有リソースにアクセスするのを防ぐための同期プリミティブです。ミューテックスは
Lock()
メソッドとUnlock()
メソッドを持ちます。Lock()
: ミューテックスをロックします。既にロックされている場合、現在のゴルーチンはロックが解放されるまでブロックされます。Unlock()
: ミューテックスをアンロックします。これにより、他のゴルーチンがロックを取得できるようになります。
defer
ステートメント: Go言語のdefer
ステートメントは、関数がリターンする直前に実行される関数呼び出しをスケジュールします。これは、リソースの解放(例: ファイルのクローズ、ミューテックスのアンロック)を確実に行うために非常に便利です。defer ctr.mu.Unlock()
のように記述することで、関数のどのパスを通っても確実にミューテックスがアンロックされることが保証されます。
net/http
パッケージのハンドラ登録
Goの net/http
パッケージは、HTTPサーバーを構築するための基本的な機能を提供します。HTTPリクエストを処理するためには、特定のパス(URL)に対してハンドラを登録する必要があります。
http.Handler
インターフェース:net/http
パッケージの中心的なインターフェースの一つで、ServeHTTP(ResponseWriter, *Request)
メソッドを一つだけ持ちます。このインターフェースを実装する任意の型は、HTTPリクエストを処理するハンドラとして機能できます。http.HandlerFunc
型: これはfunc(ResponseWriter, *Request)
型の関数をhttp.Handler
インターフェースに適合させるためのアダプタ(型)です。http.HandlerFunc(myFunction)
のようにキャストすることで、通常の関数をhttp.Handler
として扱うことができます。http.Handle(pattern string, handler Handler)
: 指定されたパターン(URLパス)に対して、http.Handler
インターフェースを実装するハンドラを登録します。http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))
: これはhttp.Handle
の糖衣構文であり、func(ResponseWriter, *Request)
型の関数を直接ハンドラとして登録できます。内部的にはhttp.HandlerFunc
を使用して関数をhttp.Handler
に変換し、http.Handle
を呼び出します。より簡潔に記述できるため、一般的にこちらが推奨されます。
expvar
パッケージ
expvar
パッケージは、Goプログラムの内部状態をHTTP経由で公開するためのシンプルな標準パッケージです。プログラムの実行中にメトリクスやデバッグ情報を簡単に監視できるように設計されています。
expvar.Var
インターフェース:String() string
メソッドを持つインターフェースです。このインターフェースを実装する任意の型は、expvar
パッケージによって公開される変数として機能できます。expvar.Publish(name string, v Var)
: 指定された名前でexpvar.Var
インターフェースを実装する変数を公開します。公開された変数は、デフォルトで/debug/vars
というHTTPパスでJSON形式でアクセスできるようになります。
os.Getenv("HOME")
os
パッケージは、オペレーティングシステムとの相互作用のための機能を提供します。os.Getenv(key string)
関数は、指定された環境変数 key
の値を文字列として返します。"HOME"
は、Unix系システムにおけるユーザーのホームディレクトリのパスを示す環境変数です。これにより、プログラムが実行される環境に依存しない、よりポータブルなパス指定が可能になります。
技術的詳細
Counter
のスレッドセーフ化
変更前は、Counter
構造体は単に n int
というカウンタ変数を持っていました。ServeHTTP
メソッド内で ctr.n++
のように直接 n
をインクリメントしていましたが、これは複数のHTTPリクエストが同時にこのハンドラに到達した場合、競合状態を引き起こす可能性がありました。例えば、2つのリクエストがほぼ同時に n
の値を読み込み、それぞれがインクリメントして書き戻す場合、期待される n+2
ではなく n+1
になってしまう可能性があります(Lost Update問題)。
このコミットでは、この問題を解決するために sync.Mutex
が導入されました。
type Counter struct {
mu sync.Mutex // protects n
n int
}
mu sync.Mutex
フィールドが追加され、n
変数を保護する役割を担うことがコメントで明示されています。
そして、Counter
の String()
メソッドと ServeHTTP()
メソッドの冒頭で ctr.mu.Lock()
が呼び出され、defer ctr.mu.Unlock()
によって関数の終了時に確実にロックが解放されるように変更されました。
func (ctr *Counter) String() string {
ctr.mu.Lock()
defer ctr.mu.Unlock()
return fmt.Sprintf("%d", ctr.n)
}
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctr.mu.Lock()
defer ctr.mu.Unlock()
switch req.Method {
case "GET":
ctr.n++
fmt.Fprintf(w, "counter = %d\n", ctr.n)
case "POST":
// ... (POST処理)
}
}
これにより、Counter
の n
フィールドへのアクセス(読み取りおよび書き込み)は、常にミューテックスによって保護されるようになり、複数のゴルーチンからの同時アクセスによる競合状態が排除され、カウンタがスレッドセーフになりました。
HTTPハンドラ登録の簡素化
以前のコードでは、http.HandlerFunc
を明示的に使用して関数を http.Handler
インターフェースに適合させてから http.Handle
に渡していました。
http.Handle("/flags", http.HandlerFunc(FlagServer))
http.Handle("/args", http.HandlerFunc(ArgServer))
http.Handle("/go/hello", http.HandlerFunc(HelloServer))
http.Handle("/date", http.HandlerFunc(DateServer))
このコミットでは、これらの行が http.HandleFunc
を使用するように変更されました。
http.HandleFunc("/flags", FlagServer)
http.HandleFunc("/args", ArgServer)
http.HandleFunc("/go/hello", HelloServer)
http.HandleFunc("/date", DateServer)
http.HandleFunc
は、func(ResponseWriter, *Request)
型の関数を直接引数として受け取ることができるため、http.HandlerFunc
による型変換が不要になり、コードがより簡潔で読みやすくなりました。これは機能的な変更ではなく、Goの慣用的なコーディングスタイルへの移行です。
webroot
の初期値の変更
webroot
という flag.String
で定義された変数のデフォルト値が変更されました。
変更前:
var webroot = flag.String("root", "/home/rsc", "web root directory")
変更後:
var webroot = flag.String("root", os.Getenv("HOME"), "web root directory")
これにより、webroot
のデフォルト値が、特定のユーザーのホームディレクトリ(/home/rsc
)から、プログラムが実行される環境のユーザーのホームディレクトリ(os.Getenv("HOME")
)に動的に設定されるようになりました。これは、異なる環境でこのサンプルコードを実行する際の利便性を向上させます。
コアとなるコードの変更箇所
src/pkg/net/http/triv.go
ファイルにおける変更箇所は以下の通りです。
--- a/src/pkg/net/http/triv.go
+++ b/src/pkg/net/http/triv.go
@@ -17,6 +17,7 @@ import (
"os"
"os/exec"
"strconv"
+ "sync" // 追加: syncパッケージのインポート
)
// hello world, the web server
@@ -29,14 +30,21 @@ func HelloServer(w http.ResponseWriter, req *http.Request) {
// Simple counter server. POSTing to it will set the value.
type Counter struct {
- n int
+ mu sync.Mutex // protects n (追加: ミューテックスフィールド)
+ n int
}
// This makes Counter satisfy the expvar.Var interface, so we can export
// it directly.
-func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }
+func (ctr *Counter) String() string {
+ ctr.mu.Lock() // 追加: ロック
+ defer ctr.mu.Unlock() // 追加: アンロックの遅延実行
+ return fmt.Sprintf("%d", ctr.n)
+}
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ ctr.mu.Lock() // 追加: ロック
+ defer ctr.mu.Unlock() // 追加: アンロックの遅延実行
switch req.Method {
case "GET":
ctr.n++
@@ -110,23 +118,22 @@ func Logger(w http.ResponseWriter, req *http.Request) {
http.Error(w, "oops", 404)
}
-var webroot = flag.String("root", "/home/rsc", "web root directory")
+var webroot = flag.String("root", os.Getenv("HOME"), "web root directory") // 変更: デフォルト値の変更
func main() {
flag.Parse()
// The counter is published as a variable directly.
ctr := new(Counter)
- http.Handle("/counter", ctr) // 削除: この行は下に移動
expvar.Publish("counter", ctr)
-
+ http.Handle("/counter", ctr) // 追加: 上の行から移動
http.Handle("/", http.HandlerFunc(Logger))
http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot))))
- http.Handle("/flags", http.HandlerFunc(FlagServer)) // 変更: HandleFuncへ
- http.Handle("/args", http.HandlerFunc(ArgServer)) // 変更: HandleFuncへ
- http.Handle("/go/hello", http.HandlerFunc(HelloServer)) // 変更: HandleFuncへ
http.Handle("/chan", ChanCreate())
- http.Handle("/date", http.HandlerFunc(DateServer)) // 変更: HandleFuncへ
+ http.HandleFunc("/flags", FlagServer) // 追加: HandleFuncで登録
+ http.HandleFunc("/args", ArgServer) // 追加: HandleFuncで登録
+ http.HandleFunc("/go/hello", HelloServer) // 追加: HandleFuncで登録
+ http.HandleFunc("/date", DateServer) // 追加: HandleFuncで登録
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Panicln("ListenAndServe:", err)
コアとなるコードの解説
-
import "sync"
の追加:Counter
構造体のn
フィールドへの並行アクセスを保護するために、sync
パッケージがインポートされました。これにより、sync.Mutex
を使用できるようになります。
-
Counter
構造体へのmu sync.Mutex
フィールドの追加:type Counter struct { mu sync.Mutex; n int }
mu
はミューテックスであり、n
フィールドへのアクセスを排他的に制御します。これにより、複数のゴルーチンが同時にn
を読み書きしようとした際の競合状態を防ぎます。
-
Counter.String()
メソッドとCounter.ServeHTTP()
メソッドでのミューテックスの使用:ctr.mu.Lock()
とdefer ctr.mu.Unlock()
が各メソッドの冒頭に追加されました。Lock()
は、共有リソース(この場合はctr.n
)へのアクセスを開始する前にミューテックスをロックします。defer ctr.mu.Unlock()
は、メソッドが終了する際に(正常終了、エラー終了に関わらず)ミューテックスをアンロックすることを保証します。これにより、n
の読み取りや更新操作がアトミック(不可分)になり、並行処理安全性が確保されます。
-
webroot
変数のデフォルト値の変更:var webroot = flag.String("root", os.Getenv("HOME"), "web root directory")
- 以前はハードコードされたパス(
/home/rsc
)でしたが、os.Getenv("HOME")
を使用することで、実行環境のユーザーのホームディレクトリをデフォルト値として設定するようになりました。これにより、サンプルコードの移植性が向上します。
-
http.Handle
からhttp.HandleFunc
への移行:main
関数内で、FlagServer
,ArgServer
,HelloServer
,DateServer
といったハンドラ関数の登録にhttp.Handle("/path", http.HandlerFunc(Handler))
の形式ではなく、より簡潔なhttp.HandleFunc("/path", Handler)
の形式が使用されるようになりました。- これは機能的な変更ではなく、Goの慣用的な書き方への「現代化」であり、コードの可読性と記述性を向上させます。
関連リンク
- Go言語
net/http
パッケージ公式ドキュメント: https://pkg.go.dev/net/http - Go言語
sync
パッケージ公式ドキュメント: https://pkg.go.dev/sync - Go言語
expvar
パッケージ公式ドキュメント: https://pkg.go.dev/expvar - Go言語
os
パッケージ公式ドキュメント: https://pkg.go.dev/os - Go言語
flag
パッケージ公式ドキュメント: https://pkg.go.dev/flag
参考にした情報源リンク
- Go言語公式ドキュメント (上記「関連リンク」に記載の各パッケージのドキュメント)
- Go言語の並行処理に関する一般的な情報源 (例: Go Concurrency Patterns, Effective Goなど)
- Go言語のミューテックスに関する解説記事 (例: A Tour of Go - Mutexes)
- Go言語のHTTPハンドラに関する解説記事 (例: Writing HTTP servers in Go)```markdown
[インデックス 12647] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/http
パッケージ内の triv.go
ファイルに対する変更です。triv.go
は、net/http
パッケージの基本的な使用例や、expvar
パッケージとの連携を示すための、いわゆる「自明な(trivial)」なHTTPサーバーの例を含んでいます。具体的には、シンプルな「Hello World」サーバー、カウンタースサーバー、ロガー、ファイルサーバーなどの機能が実装されています。このファイルは、net/http
パッケージの機能をデモンストレーションするためのサンプルコード集としての役割を担っています。
コミット
このコミットは、net/http
パッケージの triv.go
ファイルに対して、いくつかの「現代化(modernizations)」を適用するものです。主な変更点は、カウンタースサーバーの並行処理安全性の確保(スレッドセーフ化)と、HTTPハンドラの登録方法の簡素化です。これにより、サンプルコードがより堅牢で、現代のGoの慣用的な書き方に沿ったものになっています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a4e6197b91e2bc16616245a3a704b89ab99db949
元コミット内容
net/http: couple more triv.go modernizations
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5834049
変更の背景
この変更の背景には、主に以下の2つの目的があります。
- カウンタースサーバーの並行処理安全性(スレッドセーフティ)の確保: 以前の
Counter
実装では、複数のHTTPリクエストが同時にカウンタの値を更新しようとした場合、競合状態(race condition)が発生し、正確なカウントが保証されない可能性がありました。これは、並行処理がGo言語の重要な特徴であるにもかかわらず、サンプルコードがそのベストプラクティスに従っていなかったことを意味します。このコミットは、sync.Mutex
を導入することで、カウンタの増減操作を排他的に保護し、並行アクセス時のデータ破損を防ぐことを目的としています。 - HTTPハンドラ登録の慣用的な書き方への移行:
http.Handle
とhttp.HandlerFunc
の組み合わせは有効ですが、Go 1.0以降ではhttp.HandleFunc
というより簡潔な関数が提供されています。これは、func(ResponseWriter, *Request)
型の関数を直接ハンドラとして登録できる糖衣構文(syntactic sugar)であり、コードの可読性と記述性を向上させます。この変更は、サンプルコードをGoの最新の慣用的な書き方に合わせることで、新規開発者にとってより良い学習リソースとなることを意図しています。 webroot
の初期値の改善:webroot
のデフォルト値がハードコードされたパス (/home/rsc
) から、ユーザーのホームディレクトリ (os.Getenv("HOME")
) に変更されました。これにより、サンプルコードがより汎用的に、異なる環境で動作するようになります。
これらの変更は、triv.go
が単なるサンプルコードではなく、Goのベストプラクティスと現代的なコーディングスタイルを示す模範となるようにするための継続的な改善の一環です。
前提知識の解説
Go言語の並行処理とsync.Mutex
Go言語は、ゴルーチン(goroutine)とチャネル(channel)を用いた並行処理を強力にサポートしています。しかし、複数のゴルーチンが共有データに同時にアクセスする場合、競合状態が発生し、予期せぬ結果やデータ破損を引き起こす可能性があります。これを防ぐために、Goの標準ライブラリには sync
パッケージが提供されており、その中に sync.Mutex
(ミューテックス)という排他制御の仕組みがあります。
- ミューテックス(Mutex): Mutual Exclusion(相互排他)の略で、複数のゴルーチンが同時に共有リソースにアクセスするのを防ぐための同期プリミティブです。ミューテックスは
Lock()
メソッドとUnlock()
メソッドを持ちます。Lock()
: ミューテックスをロックします。既にロックされている場合、現在のゴルーチンはロックが解放されるまでブロックされます。Unlock()
: ミューテックスをアンロックします。これにより、他のゴルーチンがロックを取得できるようになります。
defer
ステートメント: Go言語のdefer
ステートメントは、関数がリターンする直前に実行される関数呼び出しをスケジュールします。これは、リソースの解放(例: ファイルのクローズ、ミューテックスのアンロック)を確実に行うために非常に便利です。defer ctr.mu.Unlock()
のように記述することで、関数のどのパスを通っても確実にミューテックスがアンロックされることが保証されます。
net/http
パッケージのハンドラ登録
Goの net/http
パッケージは、HTTPサーバーを構築するための基本的な機能を提供します。HTTPリクエストを処理するためには、特定のパス(URL)に対してハンドラを登録する必要があります。
http.Handler
インターフェース:net/http
パッケージの中心的なインターフェースの一つで、ServeHTTP(ResponseWriter, *Request)
メソッドを一つだけ持ちます。このインターフェースを実装する任意の型は、HTTPリクエストを処理するハンドラとして機能できます。http.HandlerFunc
型: これはfunc(ResponseWriter, *Request)
型の関数をhttp.Handler
インターフェースに適合させるためのアダプタ(型)です。http.HandlerFunc(myFunction)
のようにキャストすることで、通常の関数をhttp.Handler
として扱うことができます。http.Handle(pattern string, handler Handler)
: 指定されたパターン(URLパス)に対して、http.Handler
インターフェースを実装するハンドラを登録します。http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))
: これはhttp.Handle
の糖衣構文であり、func(ResponseWriter, *Request)
型の関数を直接ハンドラとして登録できます。内部的にはhttp.HandlerFunc
を使用して関数をhttp.Handler
に変換し、http.Handle
を呼び出します。より簡潔に記述できるため、一般的にこちらが推奨されます。
expvar
パッケージ
expvar
パッケージは、Goプログラムの内部状態をHTTP経由で公開するためのシンプルな標準パッケージです。プログラムの実行中にメトリクスやデバッグ情報を簡単に監視できるように設計されています。
expvar.Var
インターフェース:String() string
メソッドを持つインターフェースです。このインターフェースを実装する任意の型は、expvar
パッケージによって公開される変数として機能できます。expvar.Publish(name string, v Var)
: 指定された名前でexpvar.Var
インターフェースを実装する変数を公開します。公開された変数は、デフォルトで/debug/vars
というHTTPパスでJSON形式でアクセスできるようになります。
os.Getenv("HOME")
os
パッケージは、オペレーティングシステムとの相互作用のための機能を提供します。os.Getenv(key string)
関数は、指定された環境変数 key
の値を文字列として返します。"HOME"
は、Unix系システムにおけるユーザーのホームディレクトリのパスを示す環境変数です。これにより、プログラムが実行される環境に依存しない、よりポータブルなパス指定が可能になります。
技術的詳細
Counter
のスレッドセーフ化
変更前は、Counter
構造体は単に n int
というカウンタ変数を持っていました。ServeHTTP
メソッド内で ctr.n++
のように直接 n
をインクリメントしていましたが、これは複数のHTTPリクエストが同時にこのハンドラに到達した場合、競合状態を引き起こす可能性がありました。例えば、2つのリクエストがほぼ同時に n
の値を読み込み、それぞれがインクリメントして書き戻す場合、期待される n+2
ではなく n+1
になってしまう可能性があります(Lost Update問題)。
このコミットでは、この問題を解決するために sync.Mutex
が導入されました。
type Counter struct {
mu sync.Mutex // protects n
n int
}
mu sync.Mutex
フィールドが追加され、n
変数を保護する役割を担うことがコメントで明示されています。
そして、Counter
の String()
メソッドと ServeHTTP()
メソッドの冒頭で ctr.mu.Lock()
が呼び出され、defer ctr.mu.Unlock()
によって関数の終了時に確実にロックが解放されるように変更されました。
func (ctr *Counter) String() string {
ctr.mu.Lock()
defer ctr.mu.Unlock()
return fmt.Sprintf("%d", ctr.n)
}
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctr.mu.Lock()
defer ctr.mu.Unlock()
switch req.Method {
case "GET":
ctr.n++
fmt.Fprintf(w, "counter = %d\n", ctr.n)
case "POST":
// ... (POST処理)
}
}
これにより、Counter
の n
フィールドへのアクセス(読み取りおよび書き込み)は、常にミューテックスによって保護されるようになり、複数のゴルーチンからの同時アクセスによる競合状態が排除され、カウンタがスレッドセーフになりました。
HTTPハンドラ登録の簡素化
以前のコードでは、http.HandlerFunc
を明示的に使用して関数を http.Handler
インターフェースに適合させてから http.Handle
に渡していました。
http.Handle("/flags", http.HandlerFunc(FlagServer))
http.Handle("/args", http.HandlerFunc(ArgServer))
http.Handle("/go/hello", http.HandlerFunc(HelloServer))
http.Handle("/date", http.HandlerFunc(DateServer))
このコミットでは、これらの行が http.HandleFunc
を使用するように変更されました。
http.HandleFunc("/flags", FlagServer)
http.HandleFunc("/args", ArgServer)
http.HandleFunc("/go/hello", HelloServer)
http.HandleFunc("/date", DateServer)
http.HandleFunc
は、func(ResponseWriter, *Request)
型の関数を直接引数として受け取ることができるため、http.HandlerFunc
による型変換が不要になり、コードがより簡潔で読みやすくなりました。これは機能的な変更ではなく、Goの慣用的なコーディングスタイルへの移行です。
webroot
の初期値の変更
webroot
という flag.String
で定義された変数のデフォルト値が変更されました。
変更前:
var webroot = flag.String("root", "/home/rsc", "web root directory")
変更後:
var webroot = flag.String("root", os.Getenv("HOME"), "web root directory")
これにより、webroot
のデフォルト値が、特定のユーザーのホームディレクトリ(/home/rsc
)から、プログラムが実行される環境のユーザーのホームディレクトリ(os.Getenv("HOME")
)に動的に設定されるようになりました。これは、異なる環境でこのサンプルコードを実行する際の利便性を向上させます。
コアとなるコードの変更箇所
src/pkg/net/http/triv.go
ファイルにおける変更箇所は以下の通りです。
--- a/src/pkg/net/http/triv.go
+++ b/src/pkg/net/http/triv.go
@@ -17,6 +17,7 @@ import (
"os"
"os/exec"
"strconv"
+ "sync" // 追加: syncパッケージのインポート
)
// hello world, the web server
@@ -29,14 +30,21 @@ func HelloServer(w http.ResponseWriter, req *http.Request) {
// Simple counter server. POSTing to it will set the value.
type Counter struct {
- n int
+ mu sync.Mutex // protects n (追加: ミューテックスフィールド)
+ n int
}
// This makes Counter satisfy the expvar.Var interface, so we can export
// it directly.
-func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }
+func (ctr *Counter) String() string {
+ ctr.mu.Lock() // 追加: ロック
+ defer ctr.mu.Unlock() // 追加: アンロックの遅延実行
+ return fmt.Sprintf("%d", ctr.n)
+}
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ ctr.mu.Lock() // 追加: ロック
+ defer ctr.mu.Unlock() // 追加: アンロックの遅延実行
switch req.Method {
case "GET":
ctr.n++
@@ -110,23 +118,22 @@ func Logger(w http.ResponseWriter, req *http.Request) {
http.Error(w, "oops", 404)
}
-var webroot = flag.String("root", "/home/rsc", "web root directory")
+var webroot = flag.String("root", os.Getenv("HOME"), "web root directory") // 変更: デフォルト値の変更
func main() {
flag.Parse()
// The counter is published as a variable directly.
ctr := new(Counter)
- http.Handle("/counter", ctr) // 削除: この行は下に移動
expvar.Publish("counter", ctr)
-
+ http.Handle("/counter", ctr) // 追加: 上の行から移動
http.Handle("/", http.HandlerFunc(Logger))
http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot))))
- http.Handle("/flags", http.HandlerFunc(FlagServer)) // 変更: HandleFuncへ
- http.Handle("/args", http.HandlerFunc(ArgServer)) // 変更: HandleFuncへ
- http.Handle("/go/hello", http.HandlerFunc(HelloServer)) // 変更: HandleFuncへ
http.Handle("/chan", ChanCreate())
- http.Handle("/date", http.HandlerFunc(DateServer)) // 変更: HandleFuncへ
+ http.HandleFunc("/flags", FlagServer) // 追加: HandleFuncで登録
+ http.HandleFunc("/args", ArgServer) // 追加: HandleFuncで登録
+ http.HandleFunc("/go/hello", HelloServer) // 追加: HandleFuncで登録
+ http.HandleFunc("/date", DateServer) // 追加: HandleFuncで登録
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Panicln("ListenAndServe:", err)
コアとなるコードの解説
-
import "sync"
の追加:Counter
構造体のn
フィールドへの並行アクセスを保護するために、sync
パッケージがインポートされました。これにより、sync.Mutex
を使用できるようになります。
-
Counter
構造体へのmu sync.Mutex
フィールドの追加:type Counter struct { mu sync.Mutex; n int }
mu
はミューテックスであり、n
フィールドへのアクセスを排他的に制御します。これにより、複数のゴルーチンが同時にn
を読み書きしようとした際の競合状態を防ぎます。
-
Counter.String()
メソッドとCounter.ServeHTTP()
メソッドでのミューテックスの使用:ctr.mu.Lock()
とdefer ctr.mu.Unlock()
が各メソッドの冒頭に追加されました。Lock()
は、共有リソース(この場合はctr.n
)へのアクセスを開始する前にミューテックスをロックします。defer ctr.mu.Unlock()
は、メソッドが終了する際に(正常終了、エラー終了に関わらず)ミューテックスをアンロックすることを保証します。これにより、n
の読み取りや更新操作がアトミック(不可分)になり、並行処理安全性が確保されます。
-
webroot
変数のデフォルト値の変更:var webroot = flag.String("root", os.Getenv("HOME"), "web root directory")
- 以前はハードコードされたパス(
/home/rsc
)でしたが、os.Getenv("HOME")
を使用することで、実行環境のユーザーのホームディレクトリをデフォルト値として設定するようになりました。これにより、サンプルコードの移植性が向上します。
-
http.Handle
からhttp.HandleFunc
への移行:main
関数内で、FlagServer
,ArgServer
,HelloServer
,DateServer
といったハンドラ関数の登録にhttp.Handle("/path", http.HandlerFunc(Handler))
の形式ではなく、より簡潔なhttp.HandleFunc("/path", Handler)
の形式が使用されるようになりました。- これは機能的な変更ではなく、Goの慣用的な書き方への「現代化」であり、コードの可読性と記述性を向上させます。
関連リンク
- Go言語
net/http
パッケージ公式ドキュメント: https://pkg.go.dev/net/http - Go言語
sync
パッケージ公式ドキュメント: https://pkg.go.dev/sync - Go言語
expvar
パッケージ公式ドキュメント: https://pkg.go.dev/expvar - Go言語
os
パッケージ公式ドキュメント: https://pkg.go.dev/os - Go言語
flag
パッケージ公式ドキュメント: https://pkg.go.dev/flag
参考にした情報源リンク
- Go言語公式ドキュメント (上記「関連リンク」に記載の各パッケージのドキュメント)
- Go言語の並行処理に関する一般的な情報源 (例: Go Concurrency Patterns, Effective Goなど)
- Go言語のミューテックスに関する解説記事 (例: A Tour of Go - Mutexes)
- Go言語のHTTPハンドラに関する解説記事 (例: Writing HTTP servers in Go)