A key introduction to WithTimeout in Golang

170 阅读1分钟

Context can be used to pass values across multiple APIs, or to control other goroutines to terminate when current operation is no longer needed, or indicate another goroutine to stop when specified time is reached. As of the later case, we can use WithTimeout to reach that purpose.
First of all, you need to know that context is an entity not relied on goroutines, For example, if a context is created and passed to a goroutine, when the goroutine is returned, the context's done channel already exists.

package main

import (
	"context"
	"fmt"
	"time"
)

const shortDuration = 10 * time.Second

func worker(ctx *context.Context) {
	fmt.Println("i am worker")
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
	defer cancel()

	go worker(&ctx)

	select {
	case <-ctx.Done():
		fmt.Println(ctx.Err())
	}
	fmt.Println("main done")

}

Although the worker is finished quickly enough, the main routine still is waiting at reading the done channel until 10 senconds is up, so the output is as follows,

i am worker
context deadline exceeded
main done

If put the select-case statement into worker, then we can control when to finish the worker routine,

package main

import (
	"context"
	"fmt"
	"time"
)

const shortDuration = 10 * time.Second

func worker(ctx *context.Context) {
	select {
	case <-(*ctx).Done():
		fmt.Println("worker is canceled")
	}
	fmt.Println("i am worker")
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
        // this is used to wait for the output of worker to finish 
	defer func() {
		time.Sleep(1 * time.Second)
	}()
	defer cancel()

	go worker(&ctx)

	fmt.Println("main done")
	time.Sleep(2 * time.Second)
}

After cancel is called, the worker goroutine finishes waiting at reading the done channel, so the worker is canceled executing,

main done
worker is canceled
i am worker

As shown above, the call of ctx.Done is needed to know when done channel is closed by cancel calling or timeout reached.
The http package of Golang provides a method called NewRequestWithContext with context as the first argument, then we can pass a WithTimeout context to it, controlling the termination of http request when timeout is reached or cancel() is called.