Context

Context

context包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元的外部,通过设置ctx变量值,将过期或撤销这些信号传递给被调用的程序单元。在网络编程中,若存在A调用B的API, B再调用C的API,若A调用B取消,那也要取消B调用C,通过在A,B,C的API调用之间传递Context,以及判断其状态,就能解决此问题,这是为什么gRPC的接口中带上ctx context.Context参数的原因之一。

Context结构

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}
  • Done 方法在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回。

  • Err方法返回context为什么被取消。

  • Deadline返回context何时会超时。

  • Value返回context相关的数据。

Context的衍生

Golang 内置了两个 Context 接口实现,context.Background 和 context.TODO,作为最顶层的 Parent Context,即 Root Context。

  • Background:主要用于 main() 函数、初始化以及测试代码中,作为 Context Tree 的 Root Context。
  • TODO:如果我们不知道该使用什么类型 Context 的时候,可以使用这个。
var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)

func Background() Context {
    return background
}

两者本质上都是 emptyCtx 结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的 Context。

type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
	return
}

func (*emptyCtx) Done() <-chan struct{} {
	return nil
}

func (*emptyCtx) Err() error {
	return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
	return nil
}

emptyCtx 实现了 Context 接口,但什么都没做,返回 nil 或者零值。

WithCancel

WithCancel返回一个继承的Context,这个Context在父Context的Done被关闭时关闭自己的Done通道,或者在自己被Cancel的时候关闭自己的Done。
WithCancel同时还返回一个取消函数cancel,这个cancel用于取消当前的Context。

示例:

func main() {
	gen := func(ctx context.Context) <-chan int {
		dst := make(chan int)
		n := 1
		go func() {
			for {
				select {
				case <-ctx.Done():
					return // returning not to leak the goroutine
				case dst <- n:
					n++
				}
			}
		}()
		return dst
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // cancel when we are finished consuming integers

	for n := range gen(ctx) {
		fmt.Println(n)
		if n == 5 {
			break
		}
	}
}

WithDeadline

设置过期时间,指定什么时间取消context

func main() {
    d := time.Now().Add(50 * time.Millisecond)
    ctx, cancel := context.WithDeadline(context.Background(), d)
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) //context deadline exceeded
    }
}

WithTimeout

设置时间,指定多长时间后取消context

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()

    select {
    case <-time.After(1 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        fmt.Println(ctx.Err()) // prints "context deadline exceeded"
    }
}

WithValue

携带关键信息,为全链路提供线索。

func main() {
	ctx := context.WithValue(context.Background(), "trace_id", "88888888")
	// 携带session到后面的程序中去
	ctx = context.WithValue(ctx, "session", 1)
	process(ctx)
}

func process(ctx context.Context) {
	session, ok := ctx.Value("session").(int)
	if !ok {
		fmt.Println("something wrong")
		return
	}
	if session != 1 {
		fmt.Println("session 未通过")
		return
	}
	traceID := ctx.Value("trace_id").(string)
	fmt.Println("traceID:", traceID, "-session:", session)
}

Context 使用原则

  • 不要把 Context 作为结构体成员之一,要以参数的方式进行传递。
  • 以 Context 作为形参的函数或方法,应该把 Context 作为第一个参数。
  • 给一个函数或方法传递 Context 的时候,如果不知道传递什么,就使用 context.TODO。
  • Context 的 Value 方法应该传递必要的数据。
  • Context 是线程安全的,可以放心的在多个 Goroutine 中传递。
  • 可以把一个 Context 实例传递给任意个 Gorotuine,对它执行 取消操作时,所有 Goroutine 都会接收到取消信号。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_李少侠_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值