Goのselectの中断処理(close, context)

golang

close(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

Go1.7のcontextパッケージ | SOTA