Goのselectの中断処理(close, context)
golangclose(chan)
closeしたチャネルを読むとゼロ値になるので、selectで待っているやつにまとめて送れる。
func main() {
done := make(chan bool)
wg := new(sync.WaitGroup)
waitTillDone(wg, done)
waitTillDone(wg, done)
// こんなことしなくていい
// done <- true
// done <- true
close(done)
wg.Wait()
}
func waitTillDone(wg *sync.WaitGroup, done <-chan bool) {
wg.Add(1)
go func() {
select {
case v := <-done:
fmt.Println(v) // false (ゼロ値)
wg.Done()
}
}()
}
context
key-valueの値を渡せるほかにキャンセルやタイムアウトの仕組みをもつ。
ctx := context.Background() // empty context
ctx, cancel = context.WithCancel(ctx)
ctx, cancel = context.WithDeadline(ctx, time.Now().Add(time.Second * 10))
ctx, cancel = context.WithTimeout(ctx, time.Second * 10)
ctx = context.WithValue(ctx, key, value)
ctx.Value(key).(Data)
さっきdoneで待ってたところをctx.Done()
にする。
func main() {
finished := make(chan interface{})
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // キャンセルしないとリークする
go func() {
if err := f1(ctx); err != nil {
fmt.Printf("main: %s\n", err)
} else {
fmt.Println("ok")
}
close(finished)
}()
fmt.Println("I will cancel!")
cancel()
select {
case <-finished:
fmt.Println("finished")
}
}
func f1(ctx context.Context) error {
f2(ctx)
select {
case <-ctx.Done():
fmt.Printf("f1: %s\n", ctx.Err())
return ctx.Err()
}
}
func f2(ctx context.Context) error {
select {
case <-ctx.Done():
fmt.Printf("f2: %s\n", ctx.Err())
return ctx.Err()
}
}
I will cancel!
f2: context canceled
f1: context canceled
main: context canceled
finished
ちなみに、key-valueを受けわたすために使う場合は type-safeにするために NewContextで値を詰めて、FromContextで値を取り出すということがコメントに書いてある。 また、Contextはctxという名前で第一引数として渡す。
type User struct {
name string
}
type key int
var userKey key = 0
func NewContext(ctx context.Context, u *User) context.Context {
return context.WithValue(ctx, userKey, u)
}
func FromContext(ctx context.Context) (*User, bool) {
u, ok := ctx.Value(userKey).(*User)
return u, ok
}
参考
Go Concurrency Patterns: Pipelines and cancellation - The Go Blog