[インデックス 14897] ファイルの概要
このコミットは、Go言語の標準ライブラリである time
パッケージに Timer.Reset
メソッドを追加するものです。具体的には、以下の2つのファイルが変更されています。
src/pkg/time/sleep.go
:Timer
型にReset
メソッドが追加され、既存のStop
メソッドのコメントが修正されています。src/pkg/time/sleep_test.go
:Timer.Reset
メソッドの動作を検証するための新しいテストケースTestReset
が追加されています。
コミット
このコミットは、Volker Doblerによって2013年1月17日に行われました。コミットの目的は、既存の Timer
オブジェクトを再利用し、その有効期限を再設定するための Reset
メソッドを追加することです。これにより、新しいタイマーを毎回作成するオーバーヘッドを削減し、より効率的なタイマー管理を可能にします。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/44ff17e6646ae39cf6f703bb0adaa6bd21a11cf4
元コミット内容
commit 44ff17e6646ae39cf6f703bb0adaa6bd21a11cf4
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date: Thu Jan 17 14:41:53 2013 +1100
time: add Timer.Reset
Fixes #4412.
R=adg, rsc, rogpeppe, andrewdg, bradfitz
CC=golang-dev
https://golang.org/cl/7086050
---
src/pkg/time/sleep.go | 15 +++++++++++++--
src/pkg/time/sleep_test.go | 24 ++++++++++++++++++++++++\n 2 files changed, 37 insertions(+), 2 deletions(-)
変更の背景
Go言語の time
パッケージには、特定の時間が経過した後にイベントを発生させるための Timer
型が存在します。しかし、このコミット以前は、一度作成された Timer
はその有効期限が切れるか、Stop()
メソッドで停止されると、再利用することができませんでした。
多くのアプリケーションでは、定期的に、あるいは特定の条件が満たされた後に、同じタイマーを異なる期間で再利用したいというニーズがあります。例えば、ネットワーク接続のタイムアウト処理や、ユーザーインターフェースの要素の遅延表示などです。このようなシナリオで、毎回新しい Timer
オブジェクトを作成し、ガベージコレクションの対象とすることは、パフォーマンスのオーバーヘッドやメモリ使用量の増加につながる可能性があります。
このコミットは、Go issue #4412(このコミットメッセージに記載されているイシュー番号は、現在のGitHubのイシュー番号体系とは異なる古いトラッキングシステムのものである可能性があります)で報告された、タイマーの再利用に関する要望に応えるものです。Timer.Reset
メソッドの導入により、既存の Timer
オブジェクトを効率的に再利用し、その有効期限を動的に変更できるようになりました。これにより、タイマーを頻繁に利用するアプリケーションのパフォーマンスとリソース効率が向上します。
前提知識の解説
Go言語の time
パッケージ
Go言語の time
パッケージは、時間に関する機能を提供する標準ライブラリです。時刻の表現、期間の計算、タイマー、ティックなどの機能が含まれています。
time.Duration
time.Duration
は、ナノ秒単位で時間を表す型です。例えば、100 * time.Millisecond
は100ミリ秒を表します。
time.Timer
型
time.Timer
は、単一のイベントを未来の特定の時点で発生させるためのオブジェクトです。NewTimer
関数で作成され、指定された期間が経過すると、Timer
オブジェクトの C
チャネルに現在の時刻が送信されます。
type Timer struct {
C <-chan Time
// contains filtered or unexported fields
}
C
:Timer
が期限切れになったときに時刻が送信されるチャネルです。このチャネルから値を受信することで、タイマーの期限切れを検出できます。
time.NewTimer(d Duration) *Timer
指定された期間 d
が経過した後にイベントを発生させる新しい Timer
を作成します。
(*Timer).Stop() bool
Timer
が発火するのを防ぎます。このメソッドは、タイマーが停止された場合は true
を返し、既に期限切れになっているか停止されている場合は false
を返します。Stop()
はチャネルを閉じません。これは、チャネルからの読み取りが誤って成功するのを防ぐためです。
タイマーの内部動作(runtime.Timer
)
Goのタイマーは、内部的にはランタイム(runtime
)パッケージの runtime.Timer
構造体と関連する関数によって管理されています。これらのタイマーは、Goランタイムによって効率的にスケジューリングされ、最小ヒープなどのデータ構造を使用して、次に発火するタイマーを効率的に特定します。
startTimer(t *runtime.Timer)
: ランタイムにタイマーを登録し、開始します。stopTimer(t *runtime.Timer) bool
: ランタイムからタイマーを削除し、停止します。タイマーがアクティブだった場合はtrue
を返します。nano() int64
: 現在のナノ秒単位の時刻を返します。
技術的詳細
Timer.Reset
メソッドは、既存の Timer
オブジェクトの有効期限を新しい期間 d
に変更します。その実装は以下のステップで行われます。
-
新しい期限の計算:
when := nano() + int64(d)
現在のナノ秒時刻 (nano()
) に新しい期間d
を加算することで、タイマーが次に発火する絶対時刻when
を計算します。 -
既存タイマーの停止:
active := stopTimer(&t.r)
t.r
はTimer
内部のruntime.Timer
構造体への参照です。stopTimer
関数を呼び出すことで、現在アクティブなタイマーをランタイムから停止・削除します。stopTimer
は、タイマーが停止された(つまり、まだ発火していなかった)場合にtrue
を返します。この戻り値はactive
変数に格納され、Reset
メソッドの戻り値として使用されます。 -
新しい期限の設定:
t.r.when = when
停止したタイマーのruntime.Timer
構造体のwhen
フィールドを、ステップ1で計算した新しい発火時刻に更新します。 -
タイマーの再開:
startTimer(&t.r)
更新されたruntime.Timer
をstartTimer
関数に渡し、ランタイムに再登録します。これにより、タイマーは新しい期間でカウントダウンを開始します。 -
戻り値:
return active
Reset
メソッドは、タイマーがReset
呼び出し時にアクティブだった(つまり、まだ発火していなかった)場合にtrue
を返し、既に期限切れになっていたか停止されていた場合はfalse
を返します。この戻り値は、タイマーの状態を呼び出し元に伝えるために重要です。例えば、タイマーが既に発火していた場合、Reset
はfalse
を返すため、呼び出し元はその事実に基づいて異なる処理を行うことができます。
この実装により、Timer
オブジェクト自体を再利用しながら、その内部状態(特に発火時刻)を効率的に更新することが可能になります。
コアとなるコードの変更箇所
src/pkg/time/sleep.go
--- a/src/pkg/time/sleep.go
+++ b/src/pkg/time/sleep.go
@@ -35,10 +35,10 @@ type Timer struct {
// Stop prevents the Timer from firing.
// It returns true if the call stops the timer, false if the timer has already
-// expired or stopped.
+// expired or been stopped.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.
-func (t *Timer) Stop() (ok bool) {
+func (t *Timer) Stop() bool {
return stopTimer(&t.r)
}
@@ -58,6 +58,17 @@ func NewTimer(d Duration) *Timer {
return t
}
+// Reset changes the timer to expire after duration d.
+// It returns true if the timer had been active, false if the timer had
+// expired or been stopped.
+func (t *Timer) Reset(d Duration) bool {
+ when := nano() + int64(d)
+ active := stopTimer(&t.r)
+ t.r.when = when
+ startTimer(&t.r)
+ return active
+}
+
func sendTime(now int64, c interface{}) {
// Non-blocking send of time on c.
// Used in NewTimer, it cannot block anyway (buffer).
src/pkg/time/sleep_test.go
--- a/src/pkg/time/sleep_test.go
+++ b/src/pkg/time/sleep_test.go
@@ -245,3 +245,27 @@ func TestSleepZeroDeadlock(t *testing.T) {\n }\n <-c\n }\n+\n+func TestReset(t *testing.T) {\n+\tt0 := NewTimer(100 * Millisecond)\n+\tSleep(50 * Millisecond)\n+\tif t0.Reset(150*Millisecond) != true {\n+\t\tt.Fatalf(\"resetting unfired timer returned false\")\n+\t}\n+\tSleep(100 * Millisecond)\n+\tselect {\n+\tcase <-t0.C:\n+\t\tt.Fatalf(\"timer fired early\")\n+\tdefault:\n+\t}\n+\tSleep(100 * Millisecond)\n+\tselect {\n+\tcase <-t0.C:\n+\tdefault:\n+\t\tt.Fatalf(\"reset timer did not fire\")\n+\t}\n+\n+\tif t0.Reset(50*Millisecond) != false {\n+\t\tt.Fatalf(\"resetting expired timer returned true\")\n+\t}\n+}\n```
## コアとなるコードの解説
### `src/pkg/time/sleep.go` の変更点
1. **`Stop()` メソッドのコメント修正**:
`Stop()` メソッドのコメントが `// expired or stopped.` から `// expired or been stopped.` に変更されています。これは意味的な違いはほとんどありませんが、より自然な英語表現に修正されたものです。また、`func (t *Timer) Stop() (ok bool)` から `func (t *Timer) Stop() bool` へと、戻り値の変数名 `ok` が省略されています。これはGoの慣例として、単一の戻り値でその意味が明確な場合は変数名を省略することが多いためです。
2. **`Reset(d Duration) bool` メソッドの追加**:
このコミットの主要な変更点です。
- `when := nano() + int64(d)`: 新しいタイマーの発火時刻を計算します。`nano()` は現在のシステム時刻をナノ秒単位で取得します。
- `active := stopTimer(&t.r)`: 既存のタイマーを停止し、そのタイマーが停止時にアクティブだったかどうか(つまり、まだ発火していなかったか)を示すブール値を `active` に格納します。
- `t.r.when = when`: 内部のランタイムタイマー構造体の発火時刻を更新します。
- `startTimer(&t.r)`: 更新されたタイマーをランタイムに再登録し、新しい期間でカウントダウンを開始させます。
- `return active`: `Reset` メソッドは、タイマーが `Reset` 呼び出し時にアクティブだった場合に `true` を、既に期限切れか停止済みだった場合に `false` を返します。
### `src/pkg/time/sleep_test.go` の変更点
1. **`TestReset()` 関数の追加**:
`Timer.Reset` メソッドの動作を検証するためのテストケースです。
- **未発火タイマーのリセット**:
- `t0 := NewTimer(100 * Millisecond)`: 100ミリ秒後に発火するタイマーを作成。
- `Sleep(50 * Millisecond)`: 50ミリ秒待機。タイマーはまだ発火していない状態。
- `if t0.Reset(150*Millisecond) != true`: 未発火のタイマーを150ミリ秒にリセット。この時 `Reset` は `true` を返すはずなので、それを検証。
- `Sleep(100 * Millisecond)`: さらに100ミリ秒待機。合計150ミリ秒経過。
- `select { case <-t0.C: t.Fatalf("timer fired early") default: }`: タイマーがまだ発火していないことを確認。リセット後の期間は150ミリ秒なので、この時点では発火していないはず。
- `Sleep(100 * Millisecond)`: さらに100ミリ秒待機。合計250ミリ秒経過。リセット後の期間(150ミリ秒)は既に経過している。
- `select { case <-t0.C: default: t.Fatalf("reset timer did not fire") }`: タイマーが発火したことを確認。
- **発火済みタイマーのリセット**:
- `if t0.Reset(50*Millisecond) != false`: 既に発火したタイマーを50ミリ秒にリセット。この時 `Reset` は `false` を返すはずなので、それを検証。
このテストケースは、`Reset` メソッドが期待通りにタイマーの期間を変更し、適切な戻り値を返すことを確認しています。
## 関連リンク
- Go CL (Code Review) リンク: [https://golang.org/cl/7086050](https://golang.org/cl/7086050)
## 参考にした情報源リンク
- GitHubコミットページ: [https://github.com/golang/go/commit/44ff17e6646ae39cf6f703bb0adaa6bd21a11cf4](https://github.com/golang/go/commit/44ff17e6646ae39cf6f703bb0adaa6bd21a11cf4)
- Go言語 `time` パッケージ公式ドキュメント (現在のバージョン): [https://pkg.go.dev/time](https://pkg.go.dev/time) (当時のドキュメントとは異なる可能性がありますが、概念理解に役立ちます)
- Go言語のタイマーとスケジューリングに関する一般的な情報 (例: Goのランタイム内部に関する記事など)