The context background

Concurrency with Go is convenient because of goroutine, but it also raises another question. When we do a time-consuming asynchronous operation, how do we terminate the operation within the agreed time and return a custom result? This is how we terminate a Goroutine (because unlike OS threads, goroutine does not have an active interrupt), and this is where context comes in.

Context comes from Google and was added to the standard library in version 1.7. According to the official documentation, it is a global context for a request, carries expiration dates, manual cancellations, etc., and contains a concurrent safe map for carrying data. Context’s API is relatively simple, and I’ll show you how to use it in specific scenarios.

Scenario 1: Request link transmission

In general, our root context will be constructed at the entrance of the request as follows

ctx := context.Background()
Copy the code

If you are not sure whether you need a global context, you can use the following function to construct it

ctx := context.TODO()
Copy the code

But it can’t be nil.

The use of the pass value is as follows

package main

import (
	"context"
	"fmt"
)

func func1(ctx context.Context) {
	ctx = context.WithValue(ctx, "k1"."v1")
	func2(ctx)
}
func func2(ctx context.Context) {
	fmt.Println(ctx.Value("k1").(string))
}

func main() {
	ctx := context.Background()
	func1(ctx)
}
Copy the code

We assign k1 to v1 in func1 by WithValue(parent Context, key, val interface{}) Context, The function func2 below gets the Value of k1 via ctx.Value(key interface{}) interface{}, which is relatively simple. Here’s a question: If I assign in func2, can I get the value in func1? The answer is no, context can only carry values from top to bottom, and that’s one thing to be aware of.

Scenario 2: Cancel time-consuming operations to release resources in a timely manner

Consider the question, how do we cancel a time-consuming operation without the context package? I’ve simulated two ways of writing it

  • In network interaction scenarios, SetReadDeadline, SetWriteDeadline, and SetDeadline are used to cancel timeout

timeout := 10 * time.Second
t = time.Now().Add(timeout)
conn.SetDeadline(t)
Copy the code
  • Time-consuming operation scenario, simulated by SELECT
package main

import (
	"errors"
	"fmt"
	"time"Func func1() error {respC := make(chan int) // handle logic gofuncSleep(time.second * 3) respC < -10 close(respC)}() {time.sleep (time.second * 3) respC < -10 close(respC)}case r := <-respC:
		fmt.Printf("Resp: %d\n", r)
		return nil
	case <-time.After(time.Second * 2):
		fmt.Println("catch timeout")
		return errors.New("timeout")
	}
}

func main() {
	err := func1()
	fmt.Printf("func1 error: %v\n", err)
}
Copy the code

The above two methods are often used in engineering practice. Let’s look at how to use context for active cancellation, timeout cancellation, and multiple timeouts

  • Take the initiative to cancel
package main

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time") func func1(CTX context.context, WG * sync.waitgroup) error {defer Wg.done () respC := make(chan int) // Process logic gofunc() {time.sleep (time.second * 5) respC < -10}()case <-ctx.Done():
		fmt.Println("cancel")
		return errors.New("cancel")
	case r := <-respC:
		fmt.Println(r)
		return nil
	}
}

func main() { wg := new(sync.WaitGroup) ctx, cancel := context.WithCancel(context.Background()) wg.Add(1) go func1(ctx, Wg) time.sleep (time.second * 2) // Cancel () // Wait for goroutine to exit Wg.wait ()}Copy the code
  • Timeout to cancel
package main

import (
	"context"
	"fmt"
	"time") func func1(ctx context.Context) { hctx, hcancel := context.WithTimeout(ctx, Time.Second*4) defer hcancel() resp := make(chan struct{}, 1) // gofuncSecond * 10) resp < -struct {}{}}() select {//case <-ctx.Done():
	//		fmt.Println("ctx timeout")
	//		fmt.Println(ctx.Err())
	case <-hctx.Done():
		fmt.Println("hctx timeout")
		fmt.Println(hctx.Err())
	case v := <-resp:
		fmt.Println("test2 function handle done")
		fmt.Printf("result: %v\n", v)
	}
	fmt.Println("test2 finish")
	return

}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
	defer cancel()
	func1(ctx)
}
Copy the code

In this case, it is also ok to determine Done() of only one context, but make sure that both cancel functions are called

Matters needing attention

  • Context can only pass values from top to bottom, not vice versa.
  • If there is a cancel, be sure to call it, otherwise it will cause resource leaks, such as timer leaks.
  • Context must not be nil. If unsure, you can generate an empty context using context.todo ().

The above is context analysis, mainly from the use level, so that you have an intuitive understanding, so that you can use flexibly in the project, the next will be analyzed from the source level.

The resources

Golang official package

Go Concurrency Patterns: Context

Etcd client timeout handling sample code