Go的Context用法

Type Context

done Channel 机制类似实现取消机制, 一层封装,

解决问题

  1. 类似于done Channel, 取消goroutinue
  2. 传递取消的原因: 超时或者任务完成
  3. 优雅地组成取消机制的调度树(所有Ctx只能向下触发取消机制)
type Context interface {
	// ok表示是否有设置deadline
	DeadLine() (deadline time.Time, ok bool) 

	Done() <-chan struct{}

	Err() error

	Value(key interface{}) interface{}
}
  • Go 开发人员, 认为这种取消机制只要是用在请求方案场景(request-scoped), 所以拓展了Value 方法
  • 不提供取消api, 避免接收者越界进行取消动作

两种机制

  • 提供取消机制, 和取消原因
  • 提供数据包传递机制

使用惯例

  • 默认放在传入参数第一个

取消机制

因为context 是一个接口, 所以传递过程是无有效方法改变内容的, 所以提供以下api进行修改.
当前使用者可以通过包裹父级Context实现, 自己定制的Context给下层使用.
各层都可以实现自己对子context的控制, 从而形成了一个优雅的调度栈

	func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
	func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
	func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

顶层实例Ctx方法

	// 返回一个空的Context
	func Background() Context

	// 代码开发过程中的一个占位符, 也是返回空的Context. 不应该出现在生产代码里
	// 千万不要传nil, 不知道传什么就放Todo()
	func TODO() Context

Example:

package main

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

func main() {
	UseContextCancel(5 * time.Second)
	UseContextCancel(10 * time.Second)
	// Output:
	// run away success: context deadline exceeded
	// context canceled
	// hello, Ian
	// bye
}

func UseContextCancel(runInTime time.Duration) {

	wg := sync.WaitGroup{}
	ctx, cancel := context.WithCancel(context.Background())

	wg.Add(1)
	go func() {
		defer wg.Done()

		if err := runAwayOrHi(ctx, "Ian", runInTime); err != nil {
			fmt.Println(fmt.Sprintf("run away success: %s", err))
			cancel()
		}
	}()

	wg.Wait()

	wg.Add(1)
	go func() {
		defer wg.Done()

		if err := sayBye(ctx); err != nil {
			fmt.Println(err)
		}
	}()
	wg.Wait()
}

func runAwayOrHi(ctx context.Context, who string,
	runInTime time.Duration) error {

	// run away in time, so avoid to say hi QAQ!
	ctx, cancel := context.WithTimeout(ctx, runInTime)
	defer cancel()

	hi, err := sayHi(ctx, who)
	if err != nil {
		return err
	}

	fmt.Println(hi)
	return nil
}

func sayHi(ctx context.Context, who string) (words string, err error) {

	shyTime := 7 * time.Second
	// use deadline, avoid wasting time to wait
	if deadline, ok := ctx.Deadline(); ok {
		if deadline.Sub(time.Now().Add(shyTime)) < 0 {
			return "", context.DeadlineExceeded
		}
	}

	shying := time.NewTimer(shyTime)
	select {
	case <-ctx.Done():
		return "", ctx.Err()
	case <-shying.C:
		return fmt.Sprintf("hello, %s", who), nil
	}
}

func sayBye(ctx context.Context) error {

	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		fmt.Println("bye")
		return nil
	}
}

传递数据

在context里面实现, 这种非类型安全的key-value是很有争议性的.
一个是非类型安全, 主要还是为什么需要这个数据包功能.

Go官方给的理由

Use context values only for request-scoped data that transits processes
and API boundaries, not for passing optional parameters to functions.

糙翻译: context 的value是用在请求(eg: http)的数据传递.

使用方法

	func WithValue(parent Context, key, val interface{}) Context

注意事项

  • 传递的值是不可变的
  • 可以定义全局Key-type和Key, 或者专门的package定义Key, 再提供API. 实现类型安全
  • 尽量使用简单的类型(string, int...)

常见的Request数据

  • Request ID
  • User ID
  • URL
  • API
  • Token

内容参考

Concurrency in Go

posted @ 2021-09-01 15:18  Gilfoyle_programer  阅读(162)  评论(0编辑  收藏  举报