質問の背景:
初期からGo言語はtimeパッケージを提供しており、その中に時間を管理するための基本的な関数、time.Sleepとtime.Afterがあります。System.sleepのある言語とは異なり、Goはそのプリミティブを使って非同期タイマーを実装しており、これがマルチスレッドでの作業において重要です。
問題点:
多くの開発者はタスク間のポーズを実現するためにtime.Sleepを不正に使用しがちですが、これは競合プログラムでは望ましくありません。Goのパラダイムでは、select/チャネルとの統合のためにチャンネルとtime.Afterを使ってイベント待機の管理を行うのが正しいです。
解決策:
time.Sleep(d)は現在のゴルーチンをd秒間ブロックしますが、これは直接的な「睡眠」です。time.After(d)は、d秒後にイベントが発生するチャネルを返します。後者の選択肢は、チャネルによるselectにおいてはるかに柔軟で、中断可能な待機やタイムアウトに便利です。
コード例:
ch := make(chan struct{}) go func() { time.Sleep(2 * time.Second) ch <- struct{}{} }() select { case <-ch: fmt.Println("done") case <-time.After(1 * time.Second): fmt.Println("timeout") }
主な特性:
time.Sleepを使用してプログラム全体をブロックできますか?
いいえ、time.Sleepは「眠り」に入るのは1つのゴルーチンだけで、他は引き続き動作します。
time.Afterがチャネルが読み取られない場合にメモリリークを引き起こすことがありますか?
はい、値が読み取られるまでタイマーは残ります — 読み取りがない場合、ガーベジコレクタはオブジェクトを削除しません。
コード例:
func leak() { for { _ = time.After(time.Hour) } }
time.Afterでイベントを受信しない場合の待機を正しくキャンセルする方法は?
time.Timerを使用し、必要に応じて手動で停止するのがベストです:
t := time.NewTimer(time.Minute) if done { t.Stop() }
ゴルーチンは作業からの信号を待ち、タイムアウトのために次のようにします:
time.Sleep(10*time.Second) doSomething()
利点:
欠点:
コードはselectとtime.Afterを通じて構築されます:
select { case <-workSignal: // 実行する case <-time.After(10 * time.Second): // タイムアウトによる実行 }
利点:
欠点: