Skip to content

Files

Latest commit

 

History

History

step06

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

STEP 6: コンテキストとキャンセル処理

キャンセル処理

とあるゴールーチンでエラーが発生した場合に、 他のゴールーチンの処理をキャンセルしたい場合があります。

Goでは、ゴールーチンのキャンセル処理のためにcontext.Contextを用います。 context.WithCancel関数でラップしたコンテキストは第2戻り値返されたキャンセル用の関数が呼び出されるか、親のコンテキストがキャンセルされるとキャンセルされます。 キャンセルされたことを知るには、context.ContextDoneメソッドから返ってくるチャネルを用います。

次の例では、2つのゴールーチンを立ち上げ、キャンセルをDoneメソッドのチャネルで伝えています。 selectは複数のチャンネルへの送受信を待機することのできる構文で、どのケースのチャネルも反応しない場合は、defaultが実行されます。

func main() {
	root := context.Background()
	ctx1, cancel := context.WithCancel(root)
	ctx2, _ := context.WithCancel(ctx1)

	var wg sync.WaitGroup
	wg.Add(2) // 2つのゴールーチンが終わるの待つため

	go func() {
		defer wg.Done()
		for {
			select {
			case <-ctx2.Done():
				fmt.Println("cancel goroutine1")
				return
			default:
				fmt.Println("waint goroutine1")
				time.Sleep(500 * time.Millisecond)
			}
		}
	}()

	go func() {
		defer wg.Done()
		for {
			select {
			case <-ctx2.Done():
				fmt.Println("cancel goroutine2")
				return
			default:
				fmt.Println("waint goroutine2")
				time.Sleep(500 * time.Millisecond)
			}
		}
	}()

	time.Sleep(2 * time.Second)
	cancel()
	wg.Wait()
}

errgroup.Groupを使ったキャンセル処理

errgroup.Grouperrgroup.WithContextを用いることで、エラーが起きた際に処理をキャンセルすることができます。

func main() {
	root := context.Background()
	eg, ctx := errgroup.WithContext(root)

	eg.Go(func() error {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("cancel goroutine1")
				return nil
			default:
				fmt.Println("waint goroutine1")
				time.Sleep(500 * time.Millisecond)
			}
		}
	})

	eg.Go(func() error {
		time.Sleep(2 * time.Second)
		return errors.New("error")
	})

	if err := eg.Wait(); err != nil {
		log.Fatal(err)
	}
}

プログラムの改造

errgroup.WithContextを用いてエラーが発生した場合のキャンセル処理をハンドリングしましょう。

実行

errgroupパッケージは外部パッケージであるため、go getコマンドでインストールする必要があります。

$ go get -u golang.org/x/sync/errgroup

次のコマンドで実行することができます。

$ go run main.go

boil関数に渡す水の量を2倍にしたりしてエラーが発生するようにしてみましょう。