リアルタイムにデータを処理するプログラムを書いていると、しばしばタイムアウトを考慮する必要があります。
そうすると、何か処理をしながら、都度タイムアウトしているかどうかチェックする必要があります。
ここではチャンネルとcontextを使う2種類の方法を紹介します。
チャンネルを使う方法
まずはチャンネルを使う方法です。
package main import ( "fmt" "time" ) func main() { // タイムアウトの場合はdoneチャンネルをクローズする。 done := make(chan interface{}) cnt := 0 // 1秒後にタイムアウトにする。 go func() { time.Sleep(1 * time.Second) close(done) }() loop: for { select { case <-done: break loop default: } // 何かリアルタイムな処理 cnt++ time.Sleep(100 * time.Millisecond) } fmt.Printf("cnt: %v\n", cnt) }
実行すると以下のようになります。
cnt: 10
ここでは、done
チャンネルを用意しています。
done
チャンネルがクローズした、イコール、タイムアウトした、という実装になっています。
select
文にdefault
のケースを用意しています。
select
文のdefault
は他のcase
のチャンネルが全てブロックしている場合に実行したい処理を書きます。
ここでは、単にselect
文を抜けるために用意しています。
contextを使う方法
先ほどはチャンネルを使いました。
次は先ほどのプログラムをcontextを使ったものに書き換えてみます。
package main import ( "context" "fmt" "time" ) func main() { // contextの機能で1秒後にタイムアウトさせる。 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() cnt := 0 loop: for { select { case <-ctx.Done(): fmt.Println(ctx.Err()) break loop default: } // 何かリアルタイムな処理 cnt++ time.Sleep(100 * time.Millisecond) } fmt.Printf("cnt: %v\n", cnt) }
実行すると以下のようになります。
context deadline exceeded cnt: 10
今回はcontext
のWithTimeout()
を利用してタイムアウトを実現しています。
タイムアウトはctx.Done()
で検出でき、WithTimeout()
でタイムアウトした場合はctx.Done()
がクローズします。
そして、context deadline exceeded
というエラーが発生することで、Done
の原因がタイムアウトであると知ることができます。
こちらの方がチャンネルを使う場合に比べてシンプルですし、必要に応じてWithValue()
などの利用、子プロセスのキャンセルもできるためより便利といえますね。