go context标准库

context包在Go1.7版本时加入到标准库中。其设计目标是给Golang提供一个标准接口来给其他任务发送取消信号和传递数据。其具体作用为:

  • 可以通过context发送取消信号。

  • 可以指定截止时间(Deadline),context在截止时间到期后自动发送取消信号。

  • 可以通过context传输一些数据。

context在Golang中最主要的用途是控制协程任务的取消,但是context除了协程以外也可以用在线程控制等非协程的情况。

基本概念

context的核心是其定义的Context接口类型。围绕着Context接口类型存在两种角色:

  • 父任务:创建Context,将Context对象传递给子任务,并且根据需要发送取消信号来结束子任务。

  • 子任务:使用Context类型对象,当收到父任务发来的取消信号,结束当前任务并退出。

接下来我们从这两个角色的视角分别看一下Context对象。

context接口

type Context interface {  Deadline() (deadline time.Time, ok bool)  Done() chan struct{}  Err() error  Value(key interface{}) interface{} }
  • Deadline方法需要返回当前Context被取消的时间,也就是完成工作的截止时间(deadline);

  • Done方法需要返回一个Channel,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done方***返回同一个Channel;

  • Err方***返回当前Context结束的原因,它只会在Done返回的Channel被关闭时才会返回非空的值;

    • 如果当前Context被取消就会返回Canceled错误;

    • 如果当前Context超时就会返回DeadlineExceeded错误;

  • Value方***从Context中返回键对应的值,对于同一个上下文来说,多次调用Value 并传入相同的Key会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据;

Background()和TODO()

Go内置两个函数:Background()和TODO(),这两个函数分别返回一个实现了Context接口的background和todo。我们代码中最开始都是以这两个内置的上下文对象作为最顶层的partent context,衍生出更多的子上下文对象。

  • Background()主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。

  • TODO(),它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个。

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

With函数

  • WithCancel函数,传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context

  • WithDeadline函数,和WithCancel差不多,它会多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消

  • WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思

  • WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到

Context使用原则

  • 不要把Context放在结构体中,要以参数的方式进行传递

  • 以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位]

  • 给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO

  • Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递

package main  import (  "context"  "fmt"  "time" )   func main() {  server1()  time.Sleep(time.Second) }   func server1() {  ctx, cancel := context.WithTimeout(context.Background(),time.Millisecond * 30 )// 30毫秒后过期  defer cancel()  ctx = context.WithValue(ctx, "name", "zhaohaiyu")  for i:=0;i100;i++{   time.Sleep(time.Millisecond * 10)   go server2(ctx)  }  }  func server2(ctx context.Context) {  select {  case ctx.Done():   fmt.Println("打断执行")   return  default:   name,ok := ctx.Value("name").(string)   if !ok{    fmt.Println("没有取到")    return   }   fmt.Println("name=",name)  } }