SlideShare a Scribd company logo
いまさら聞けない
select あれこれ
Go Allstars 2 (Oct 2, 2016)
牧大輔 / Daisuke Maki (lestrrat)
HDE株式会社
• 牧大輔 (lestrrat)
• 株式会社 HDE
• Perl/Go/C 等
• builderscon 12月3日 開催!

https://builderscon.io/
builderscon
• 12月3日開催!
• Web系エンジニア達に楽しんでもらいつつ、これまでの枠にとらわれない
様々な分野の技術者達が終結!
• 是非「知らなかった、を知る」ためにご来場ください。
• トーク募集中!

https://builderscon.io/builderscon/tokyo/2016/cfp
=
Web+DB Press 10/22
channel++
• 端的に言ってチャンネルは最高
• 排他処理を意識しないデータの移動
• でもチャンネル単体ではできない操作が
結構ある。
func WriteCh(ch chan int) {
ch <- 1 // Could block, bah
}
channels block
• バッファ付きチャンネルでもブロックする可能性
func ReadCh(ch chan int) {
<-ch // Could block, bah
}
channels block
• 読み込みでも書き込みがあるまでブロック
• 戻り値二つの<-chはチャンネルが閉じてあるかどうか
の確認にしか使えないので、やっぱりブロックする
つまり
• 複数のチャンネルを同時に同じgoroutineで
扱いたい場合はどうする?
go func() {
<-ch1
…
}()
go func() {
<-ch2
…
}()
チョットチガウ…
select
• Cのselect使った事ある人: アレです
• 複数のchannelの読み書きを制御してくれる
• Go中級者なら確実にマスターしてるべき
select {
case チャンネル操作:
…
case チャンネル操作:
…
default:

…
}
基本形
func CanWeRead(ch <-chan int) {
select {
case <-ch:
… // yes, we can read
}
}
読み込み
func CanWeRead(ch <-chan int) {
select {
case <-ch:
… // yes, we can read
}
}
読み込み
ポイント:defaultがないとブ
ロックするよ!
func CanWeRead(ch <-chan int) {
select {
case <-ch:
… // yes, we can read
default:
… // no, we could not read
}
}
読み込み
func CanWeRead(ch <-chan int) {
select {
case <-ch:
… // yes, we can read
default:
… // no, we could not read
}
}
読み込み
ポイント:読み込みが即時でき
ない場合はデフォルトが実行さ
れるので、実質的なノンブロッ
キングI/O
func CanWeRead(ch <-chan int) {
select {
case v, ok := <-ch:
if !ok { // channel is closed
…
}
fmt.Println(v)
}
}
値を使う
func CanWeRead(ch <-chan int) {
select {
case v, ok := <-ch:
if !ok { // channel is closed
…
}
fmt.Println(v)
}
}
値を使う
ポイント:代入もできるよ!
func CanWeWrite(ch chan int) {
select {
case ch<-1:
… // yes, we can write
default:
… // no, we could not write
}
}
書き込み
func CanWeWrite(ch chan int) {
select {
case ch<-1:
… // yes, we can write
default:
… // no, we could not write
}
}
書き込み
ポイント:書き込みが即時でき
ない場合はデフォルトが実行さ
れるので、実質的なノンブロッ
キングI/O
ところで
• nilなチャンネルに対する書き込み・読み込み
の操作ってどうなるか知ってますか?
var ch chan int
<-ch // 初期化してないのでch = nil
• panic?
• ブロック?
• その他?
nil channel
• 読み込み=ブロック
• 書き込み=ブロック
var ch chan int
select {
case <-ch:
…
}
…ということは
var ch chan int
select {
case <-ch:
…
}
…ということは
ポイント:この読み込みが成功するこ
とはない!
select {
case <-ch1:
…
case <-ch2:
ch1 = nil // Disable previous case
}
特定のケースを無効にする
select {
case <-ch1:
…
case <-ch2:
ch1 = nil // Disable previous case
}
特定のケースを無効にする
ポイント:最初は普通にch1から読み
込み可能
select {
case <-ch1:
…
case <-ch2:
ch1 = nil // Disable previous case
}
特定のケースを無効にする
ポイント:ch2から読み込みができた
らch1を無効にしてる。これ以降ch1の
caseは実行されることがない
ch1 = nil
select {
case <-ch1:
…
case <-ch2:
ch1 = … // Enable previous case
}
逆バージョン
ch1 = nil
select {
case <-ch1:
…
case <-ch2:
ch1 = … // Enable previous case
}
逆バージョン
ポイント:最初はch1 = nilなので絶
対にこのcaseは実行されない。有効な
チャンネルを代入すれば動く。
ch1 = nil
select {
case <-ch1:
…
case <-ch2:
ch1 = … // Enable previous case
}
逆バージョン
ポイント:ch1 に有効なチャンネルを
代入すればch1のcaseも有効になる
パターン
• selectを使ったよくつかうパターン集
t := time.NewTimer(30*time.Second)
defer t.Stop()
select {
case <-t.C:
return errors.New(“timeout”)
case ch <- 1:

return nil
}
書き込みタイムアウト
t := time.NewTimer(30*time.Second)
defer t.Stop()
select {
case <-t.C:
return errors.New(“timeout”)
case ch <- 1:

return nil
}
書き込みタイムアウト
ポイント: 書き込みが成功したらエ
ラー無しで脱出
t := time.NewTimer(30*time.Second)
defer t.Stop()
select {
case <-t.C:
return errors.New(“timeout”)
case ch <- 1:

return nil
}
書き込みタイムアウト
ポイント: 書き込むより先にタイム
アウトになったらエラーを返す
t := time.NewTimer(30*time.Second)
defer t.Stop()
for {
select {
case <-t.C: // 30秒後にここにくる
return
default:

// タイムアウトするまで行う操作
}
}
タイムアウトまでループ
t := time.NewTimer(30*time.Second)
defer t.Stop()
for {
select {
case <-t.C: // 30秒後にここにくる
return
default:

// タイムアウトするまで行う操作
}
}
タイムアウトまでループ
ポイント:for で囲まないと一回
defaultの操作をしたら終わっちゃう
ので、無限ループにする
t := time.NewTimer(30*time.Second)
defer t.Stop()
for {
select {
case <-t.C: // 30秒後にここにくる
return
default:

// タイムアウトするまで行う操作
}
}
タイムアウトまでループ
ポイント: タイマーからお知らせが
来たらこのループから脱出
t := time.NewTicker(time.Second)
defer t.Stop()
for {
select {
case <-t.C: // 1秒ごとにここにくる
// 定期的な処理内容
}
}
定期的に実行
t := time.NewTicker(time.Second)
defer t.Stop()
for {
select {
case <-t.C: // 1秒ごとにここにくる
// 定期的な処理内容
}
}
定期的に実行
ポイント:TimerではなくTicker
ところで
• closeされたチャンネルから読み込むとどうなる?
time.AfterFunc(5*time.Second, func() {
close(done)
})
for {
select {
case <-done:

return
default:
// 定期的な処理内容
}
}
“done”チャンネル
time.AfterFunc(5*time.Second, func() {
close(done)
})
for {
select {
case <-done:

return
default:
// 定期的な処理内容
}
}
“done”チャンネル
ポイント: チャンネルをcloseすると
読み込みブロックが解除される
time.AfterFunc(5*time.Second, func() {
close(done)
})
for {
select {
case <-done:

return
default:
// 定期的な処理内容
}
}
“done”チャンネル
ポイント:戻り値は無効だが、ブロッ
クは解けるのでループを脱出できる
for {
select {
case <-ctx.Done():

return
default:
// 定期的な処理内容
}
}
Go 1.7からはContext
Context
• context.Contextができてからselectを使う事
がより多くなった
Context
• I/Oとか関係なくても、「途中で処理を止め
る」系のコードでものすごく良く使う
例:「リストを処理」
for _, data := range list {
select {
case <-ctx.Done():

return
default:
process(data)
}
}
例:「リストを処理」
for _, data := range list {
select {
case <-ctx.Done():

return
default:
process(data)
}
}
ポイント:リスト要素を処理する毎に
Doneをチェックし、必要であれば終了
外道
• いよいよselectの変態的な使いかたを必要とする時
reflect.Select
• 動的にselect文を構築
• プログラム実行中にcaseの数や内容を変更できる
reflect.Select([]reflect.SelectCase{
reflect.SelectCase{Chan: ch1},
reflect.SelectCase{Chan: ch2},
reflect.SelectCase{Chan: ch3},
})
Web+DB Press 10/22
=
まとめ
• チャンネルとselectの複合技で表現の幅が
広がる
• 初級→中級になるには押さえておきたい。
builderscon
• 12月3日開催!
• トーク募集中!

https://builderscon.io/builderscon/tokyo/2016/cfp
Questions?

More Related Content

いまさら聞けないselectあれこれ