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

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

このコミットは、Go言語の公式ドキュメントに含まれるコードウォーク(codewalk)のサンプルプログラム doc/codewalk/urlpoll.go に対する変更です。urlpoll.go は、複数のURLを定期的にポーリングし、その状態を監視するシンプルな並行処理の例を示しています。このファイルは、Go言語のgoroutineとchannelを用いた並行処理のパターン、およびtimeパッケージの利用方法を学習するための教材として機能します。

コミット

commit 1de49311135e8e07e16f065f5c03fab9d7dae5e8
Author: Stefan Nilsson <snilsson@nada.kth.se>
Date:   Thu Jan 19 14:45:59 2012 +1100

    doc/codewalk: update urlpoll to use time.Duration.
    
    R=adg
    CC=golang-dev
    https://golang.org/cl/5545061

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

https://github.com/golang/go/commit/1de49311135e8e07e16f065f5c03fab9d7dae5e8

元コミット内容

doc/codewalk: update urlpoll to use time.Duration.

変更の背景

このコミットの主な目的は、doc/codewalk/urlpoll.go サンプルコードにおける時間間隔の表現方法を改善し、Go言語の標準的なプラクティスに合わせることです。以前のコードでは、時間間隔をナノ秒単位のint64型で直接扱っていました。これは、数値としては正しいものの、その数値が「時間」を表すことをコードの読み手に明確に伝えることができませんでした。また、異なる時間単位(秒、ミリ秒など)を扱う際に、誤った単位での計算ミスを引き起こす可能性がありました。

Go言語のtimeパッケージには、期間(duration)を表現するための専用のtime.Duration型が用意されています。この型を使用することで、時間に関する値をより型安全に、かつ意図を明確にして扱うことができます。この変更は、サンプルコードの品質と可読性を向上させ、Go言語のベストプラクティスを示すことを意図しています。

前提知識の解説

Go言語の time パッケージと time.Duration

Go言語の標準ライブラリには、時間と日付を扱うためのtimeパッケージが含まれています。このパッケージには、特定の時点を表すtime.Time型と、時間間隔(期間)を表すtime.Duration型があります。

  • time.Duration: これはint64のエイリアス型であり、ナノ秒単位で期間を表します。しかし、単なるint64とは異なり、time.Duration型は時間に関する演算(加算、減算、乗算、除算)を型安全に行うためのメソッドを提供します。例えば、time.Secondtime.Minutetime.Hourといった定数と組み合わせて、60 * time.Secondのように記述することで、60秒という期間を直感的に表現できます。これにより、コードの可読性が向上し、単位変換のミスを防ぐことができます。

Go言語の型安全性

Go言語は静的型付け言語であり、型安全性を重視しています。型安全性とは、プログラムが意図しない型の値を扱ったり、型変換の誤りによって予期せぬ動作をしたりするのを防ぐ特性です。int64で時間間隔を表現する場合、そのint64が時間なのか、単なる数値なのかがコードからは判別しにくいです。しかし、time.Duration型を使用することで、コンパイラがその値が時間間隔であることを認識し、時間に関する操作以外での誤用を防ぐことができます。これにより、バグの発生を抑制し、コードの信頼性を高めます。

Go言語の並行処理(GoroutineとChannel)

urlpoll.goはGo言語の並行処理の基本的な概念であるgoroutineとchannelを使用しています。

  • Goroutine: Go言語における軽量なスレッドのようなものです。goキーワードを関数呼び出しの前に置くことで、その関数を新しいgoroutineとして実行し、他の処理と並行して動作させることができます。
  • Channel: goroutine間で安全にデータを送受信するための通信メカニズムです。channelを使用することで、共有メモリを直接操作することなく、goroutine間の同期と通信を行うことができます。これにより、データ競合(data race)のような並行処理に起因する複雑なバグを防ぐことができます。urlpoll.goでは、pendingcompletestatusといったchannelがgoroutine間のデータフローを管理しています。

技術的詳細

このコミットでは、urlpoll.go内の時間間隔を扱う部分がint64からtime.Duration型に移行されました。具体的な変更点は以下の通りです。

  1. 定数の変更:

    • second = 1e9というナノ秒を定義していた定数が削除されました。
    • pollIntervalstatusIntervalerrTimeoutといった時間間隔を表す定数が、60 * secondのようなint64の計算から、60 * time.Secondのようにtime.Duration型の定数とtimeパッケージのSecond定数を直接使用する形に変更されました。これにより、これらの定数が明確に「期間」を表すことが示されます。
  2. StateMonitor関数の引数型の変更:

    • func StateMonitor(updateInterval int64)func StateMonitor(updateInterval time.Duration)に変更されました。これにより、StateMonitor関数が期待するupdateIntervalが時間間隔であることが明確になり、呼び出し側での誤ったint64値の渡し方をコンパイラが検出できるようになります。
  3. Resource構造体のerrCountフィールドの型変更:

    • errCount int64errCount intに変更されました。これは、time.Durationとの乗算を考慮した変更です。Go言語では、time.Durationintの乗算は定義されていますが、time.Durationint64の乗算は直接定義されていません(time.Durationint64のエイリアスですが、型システム上は異なる型として扱われます)。errCountはエラー回数を表すため、負の値を取ることはなく、int型で十分な範囲をカバーできます。
  4. Resource.Sleepメソッドの実装変更:

    • time.Sleep(pollInterval + errTimeout*r.errCount)time.Sleep(pollInterval + errTimeout*time.Duration(r.errCount))に変更されました。
    • ここで、r.errCountint型)がtime.Duration型に明示的に型変換されています。これにより、errTimeouttime.Duration)とtime.Duration(r.errCount)time.Duration)の乗算が可能になり、結果としてtime.Duration型の値がtime.Sleepに渡されるようになります。この変更により、時間計算の型安全性が確保されます。

これらの変更は、コードの可読性を大幅に向上させ、時間に関するバグ(例えば、単位の誤解による計算ミス)のリスクを低減します。また、Go言語の標準的な慣習に従うことで、将来的なメンテナンスも容易になります。

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

--- a/doc/codewalk/urlpoll.go
+++ b/doc/codewalk/urlpoll.go
@@ -11,11 +11,10 @@ import (
 )
 
 const (
-	numPollers     = 2           // number of Poller goroutines to launch
-	second         = 1e9         // one second is 1e9 nanoseconds
-	pollInterval   = 60 * second // how often to poll each URL
-	statusInterval = 10 * second // how often to log status to stdout
-	errTimeout     = 10 * second // back-off timeout on error
+	numPollers     = 2                // number of Poller goroutines to launch
+	pollInterval   = 60 * time.Second // how often to poll each URL
+	statusInterval = 10 * time.Second // how often to log status to stdout
+	errTimeout     = 10 * time.Second // back-off timeout on error
 )
 
 var urls = []string{
@@ -33,7 +32,7 @@ type State struct {
 // StateMonitor maintains a map that stores the state of the URLs being
 // polled, and prints the current state every updateInterval nanoseconds.
 // It returns a chan State to which resource state should be sent.\n-func StateMonitor(updateInterval int64) chan<- State {
+func StateMonitor(updateInterval time.Duration) chan<- State {
 	updates := make(chan State)\n 	urlStatus := make(map[string]string)\n 	ticker := time.NewTicker(updateInterval)\n@@ -61,7 +60,7 @@ func logState(s map[string]string) {
 // Resource represents an HTTP URL to be polled by this program.\n type Resource struct {
 	url      string
-\terrCount int64
+\terrCount int
 }\n 
 // Poll executes an HTTP HEAD request for url
@@ -79,8 +78,8 @@ func (r *Resource) Poll() string {
 
 // Sleep sleeps for an appropriate interval (dependant on error state)
 // before sending the Resource to done.\n-func (r *Resource) Sleep(done chan *Resource) {
-\ttime.Sleep(pollInterval + errTimeout*r.errCount)\n+func (r *Resource) Sleep(done chan<- *Resource) {
+\ttime.Sleep(pollInterval + errTimeout*time.Duration(r.errCount))\n \tdone <- r
 }\n 
 \n@@ -93,18 +92,18 @@ func Poller(in <-chan *Resource, out chan<- *Resource, status chan<- State) {
 }\n 
 func main() {
-\t// create our input and output channels
+\t// Create our input and output channels.\n \tpending, complete := make(chan *Resource), make(chan *Resource)\n \n-\t// launch the StateMonitor
+\t// Launch the StateMonitor.\n \tstatus := StateMonitor(statusInterval)\n \n-\t// launch some Poller goroutines
+\t// Launch some Poller goroutines.\n \tfor i := 0; i < numPollers; i++ {\n \t\tgo Poller(pending, complete, status)\n \t}\n \n-\t// send some Resources to the pending queue
+\t// Send some Resources to the pending queue.\n \tgo func() {\n \t\tfor _, url := range urls {\n \t\t\tpending <- &Resource{url: url}\n```

## コアとなるコードの解説

### 定数定義の変更

```diff
-	second         = 1e9         // one second is 1e9 nanoseconds
-	pollInterval   = 60 * second // how often to poll each URL
-	statusInterval = 10 * second // how often to log status to stdout
-	errTimeout     = 10 * second // back-off timeout on error
+	pollInterval   = 60 * time.Second // how often to poll each URL
+	statusInterval = 10 * time.Second // how often to log status to stdout
+	errTimeout     = 10 * time.Second // back-off timeout on error
  • second定数(1秒をナノ秒で表すint64)が削除されました。
  • pollIntervalstatusIntervalerrTimeoutの各定数が、time.Secondというtime.Duration型の定数を用いて直接定義されるようになりました。これにより、これらの値が時間間隔であることをコード上で明確に表現し、型安全性を高めています。例えば、60 * time.Secondは「60秒間」という期間を意味します。

StateMonitor関数のシグネチャ変更

-func StateMonitor(updateInterval int64) chan<- State {
+func StateMonitor(updateInterval time.Duration) chan<- State {
  • StateMonitor関数のupdateInterval引数の型がint64からtime.Durationに変更されました。これにより、この関数が期待する引数が時間間隔であることが明確になり、呼び出し側で誤った数値が渡されることを防ぎます。

Resource構造体のerrCountフィールドの型変更

 type Resource struct {
 	url      string
-	errCount int64
+	errCount int
 }
  • Resource構造体内のerrCountフィールドの型がint64からintに変更されました。errCountはエラーの回数を表すため、通常は非負の整数であり、int型で十分な範囲をカバーできます。この変更は、後述するtime.Durationとの乗算をより自然に行うための準備でもあります。

Resource.Sleepメソッドの実装変更

-func (r *Resource) Sleep(done chan *Resource) {
-	time.Sleep(pollInterval + errTimeout*r.errCount)
+func (r *Resource) Sleep(done chan<- *Resource) {
+	time.Sleep(pollInterval + errTimeout*time.Duration(r.errCount))
 	done <- r
 }
  • doneチャネルの型がchan *Resourceからchan<- *Resourceに変更されました。これは、このチャネルがSleepメソッド内で送信専用として使用されることを明示するもので、機能的な変更ではありませんが、コードの意図を明確にします。
  • time.Sleepに渡す引数の計算式が変更されました。
    • 変更前: pollInterval + errTimeout*r.errCount
    • 変更後: pollInterval + errTimeout*time.Duration(r.errCount)
  • r.errCountint型)がtime.Duration(r.errCount)として明示的にtime.Duration型に変換されています。これにより、errTimeouttime.Duration)とtime.Duration(r.errCount)time.Duration)の乗算が可能になり、結果としてtime.Duration型の値がtime.Sleep関数に渡されます。これは、エラー回数に応じてバックオフする時間を計算する際に、型安全な時間演算を適用するための重要な変更です。

main関数のコメント変更

 func main() {
-	// create our input and output channels
+	// Create our input and output channels.
 	pending, complete := make(chan *Resource), make(chan *Resource)
 
-	// launch the StateMonitor
+	// Launch the StateMonitor.
 	status := StateMonitor(statusInterval)
 
-	// launch some Poller goroutines
+	// Launch some Poller goroutines.
 	for i := 0; i < numPollers; i++ {
 		go Poller(pending, complete, status)
 	}
 
-	// send some Resources to the pending queue
+	// Send some Resources to the pending queue.
 	go func() {
 		for _, url := range urls {
 			pending <- &Resource{url: url}
  • main関数内のコメントが、小文字始まりから大文字始まりに変更され、末尾にピリオドが追加されました。これは、Go言語の公式ドキュメントやサンプルコードにおけるコメントスタイルの統一を目的とした、軽微な整形変更です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (timeパッケージ)
  • Go言語の型システムに関する一般的な知識
  • Go言語の並行処理に関する一般的な知識
  • GitHubのコミット履歴と差分表示
  • Go言語のコードレビュー慣習 (コメントスタイルなど)
  • Go言語のtime.Durationに関するブログ記事やチュートリアル (例: "Go time.Duration" で検索して得られる情報)