並行処理について(teeチャンネル編)
1つのチャンネル値を2つに別けて別の場所で使用したいとき、teeチャンネルを使うことについて、備忘録とする。
参考にしたのはこちら。
ネットで調べるのもいいけど、情報の正しさを判断できないなら、この本に記載されてるコードを見た方が良いと思う。
取り扱うもの
- teeチャンネルについて
- orDoneチャンネル goroumaru41gou.hatenablog.com
テストコード
github.com/goroumaru/test-code
teeチャンネルとは?
ひとつのチャンネルデータを、ふたつのチャンネルへ分割する。
だから、1入力2出力を"T"という文字で表して、teeチャンネル。
S/Wだけでなく、H/W分野でもteeチャンネルって言葉は使われてる。
teeチャンネル
// Tee : func Tee(ctx context.Context, in <-chan interface{}) (_, _ <-chan interface{}) { out1 := make(chan interface{}) out2 := make(chan interface{}) go func() { defer close(out1) defer close(out2) for val := range orDoneChannel.OrDoneCtx(ctx, in) { var out1, out2 = out1, out2 // コピーしてローカル変数用意 for i := 0; i < 2; i++ { // out1とout2を確実に選択するため。 select { case out1 <- val: out1 = nil // コピー側へnil代入し、out1チャンネルをブロックさせる。 // (=out2を選択させる) case out2 <- val: out2 = nil // コピー側へnil代入し、out2チャンネルをブロックさせる。 // (=out1を選択させる) } } // out1とout2の書き込みが終わると、inチャンネルが読み込み可能となる。 // for ~ rangeのイテレーションがひとつ進むから。 } }() return out1, out2 }
なぜteeチャンネルを利用するのか?
チャンネルブロックを避けて、2チャンネルへ分岐できる。
teeチャンネル使い方
func TestTeeChannel(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 定時実行 tick := time.NewTicker(1 * time.Second) defer tick.Stop() inData := make(chan interface{}) go func() { defer close(inData) var cnt int for { select { case <-tick.C: cnt++ inData <- cnt case <-ctx.Done(): return } } }() // teeチャンネルを利用してチャンネルを分岐する out1, out2 := teeChannel.Tee(ctx, inData) // teeChannelというパッケージ名にしたので・・・ for v1 := range out1 { v2 := <-out2 fmt.Printf("2つに分割:\n(out1, ok) = (%v)\n(out2, ok) = (%v)\n", v1, v2) } }