Golang笔记 5.3 上下文 context

前言

golang 的 Context 包,是专门用来简化多个goroutine之间的上下文同步。

本篇笔记主要参考了 go blog

我正在学习酷酷的 Golang,可点此查看帖子Golang学习笔记汇总

1 库的介绍

Go 语言中的每一个请求的都是通过一个单独的 Goroutine 进行处理的,HTTP/RPC 请求的处理器往往都会启动新的 Goroutine 访问数据库和 RPC 服务,我们可能会创建多个 Goroutine 来处理一次请求,而 Context 的主要作用就是在不同的 Goroutine 之间同步请求特定的数据、同时优雅地结束上下文。

一个实际例子是,在Go服务器程序中,每个请求都会有一个goroutine去处理。然而,处理程序往往还需要创建额外的goroutine去访问后端资源,比如数据库、RPC服务等。由于这些goroutine都是在处理同一个请求,所以它们往往需要访问一些共享的资源,比如用户身份信息、认证token、请求截止时间等。而且如果请求超时或者被取消后,所有的goroutine都应该马上退出并且释放相关的资源。这种情况也需要用Context来为我们取消掉所有goroutine。

一句话:Context 包可以在不同的 Goroutine 之间同步请求数据,还能优雅地设置超时及信号来结束上下文。

2 用法

2.1 Context 定义

context 包的核心是 struct Context,声明如下:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

2.2 根 Context

开始上下文的时候是以 Background() 作为最顶层的 parent context,然后再衍生出子context。context.Background() 只是一个空的上下文,没有什么特殊功能,它是上下文中最顶层的默认值.
这些 Context 对象形成一棵树:当一个 Context 对象被取消时,继承自它的所有 Context 都会被取消。两个实现如下:

var (
	background = new(emptyCtx)
	todo = new(emptyCtx)
)

func Background() Context {
	return background
}

func TODO() Context {
	return todo
}

在多数情况下如果函数没有上下文作为入参,我们往往都会使用 context.Background() 作为起始的 Context 向下传递。

例如:

  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()
  r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})

2.3 衍生的 Context (Derived contexts)

有了如上的父Context,那么是如何衍生更多的子 Context 的呢?这就要靠 context 包为我们提供的 With 系列的函数了。

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)
func WithValue(parent Context, key, val interface{}) Context

通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。

  • WithCancel函数,传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context。
  • WithDeadline函数,和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消。
  • WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思。
  • WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到,这是我们实际用经常要用到的技巧,一般我们想要通过上下文来传递数据时,可以通过这个方法,如我们需要tarce追踪系统调用栈的时候。

例如上面的例子:

  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()
  r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})

context 会在例子所在的函数结束时调用 延迟函数 cancel() 来结束上下文,同时也可以通过超时来结束上下文。前者是请求成功回复后结束上下文,后者是请求超时未回复后结束上下文。

2.4 注意事项

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx:

func DoSomething(ctx context.Context, arg Arg) error {
	// ... use ctx ...
}

3 小结

GO 内置的 Context 包可以在不同的 Goroutine 之间同步请求数据,还能优雅地通过 WithTimeout 设置超时及 WithCancel 设置取消信号来结束上下文。

END


发布了237 篇原创文章 · 获赞 226 · 访问量 79万+

猜你喜欢

转载自blog.csdn.net/iotisan/article/details/103086920
今日推荐