go的语言中context是我们常用中常说的上下文,context在我们业务中也是经常使用的,其主要的应用1:上下文控制:io处理异常等处理,多个goroutine之间的数据交互等,2:超时控制:到某个时间点超时,过多久超时等应用。

golang的context的数据结构

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

我们简单来看一下他的结构的意思

字段

含义

Deadline

返回一个time.Time,表示当前Context应该结束的时间,ok则表示有结束时间

Done

当Context被取消或者超时时候返回的一个close的channel,告诉给context相关的函数要停止当前工作然后返回了

Err

context被取消的原因

Value

context实现共享数据存储的地方,是协程安全的

其实整个context的实现还是比较简单的,他的具体怎么实现今天就不讲了,下面我们来看他的功能玩法。


1:context手动去掉的使用

  • 根context:通过context.Background()创建
  • 子context:context.WithCancel(parentContext)创建
  • ctx,cancel:=context.WithCancel(context.Background())
  • 当前context被取消时 基于他的子context都会被取消
  • 接收取消通知 <-ctx.Done()
package main

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

func main() {
  ctx, cancel := context.WithCancel(context.Background())
  for i := 0; i < 5; i++ {
    go func(i int, ctx context.Context) {
      for {
        if canceled(ctx) {
          break
        }
        doOneWork()
      }
      fmt.Println(i, "canceled.")
    }(i, ctx)
  }
  cancel()
  time.Sleep(time.Second * 5)
}

func canceled(ctx context.Context) bool {
  select {
  case <-ctx.Done():
    fmt.Println("取消了")
    return true
  default:
    return false
  }
}

func doOneWork() {
  fmt.Println(111)
}

执行结果(可能执行多次,会有不一样的结果,那属于正常的):

111
取消了
取消了
1 canceled.
取消了
2 canceled.
取消了
4 canceled.
0 canceled.
取消了
3 canceled.

2:看一下超时取消和超过某个时间取消的功能

package main

import (
  "context"
  "fmt"
  "time"
  "math/rand"
)

func main() {
  //下面是1秒后超时
  ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  //下面是当前时间+1秒取消
  //ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1))
  doOneWork(ctx)
  defer cancel()
  fmt.Println(ctx.Err())
}

func doOneWork(ctx context.Context) {
  n := 0
  for {
    select {
    case <-ctx.Done():
      fmt.Println("stop \n")
      return
    default:
      incr := rand.Intn(5)
      n += incr
      fmt.Println(incr)
    }
  }
}

上面的代码,大家去执行,我就不复制结果了,每次执行结果不尽相同。


3:看一下context写到数据到子的context的使用

package main

import (
  "context"
  "fmt"
)

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

  process(ctx)
}

func process(ctx context.Context) {
  userId, ok := ctx.Value("user_id").(int)
  if !ok {
    fmt.Println("something wrong")
    return
  }

  if userId <= 0 {
    fmt.Println("userId 存在")
    return
  }

  traceID := ctx.Value("trace_id").(string)
  fmt.Println("traceID:", traceID, "\nuserId:", userId)
}

下面我们来看一下结果

traceID: 88888888
userId: 1

context使用我们就讲到这里了,我们业务中可能经常会用到context,学会context是极其重要的。