七叶笔记 » golang编程 » Golang中Context归纳理解

Golang中Context归纳理解

为什么使用context

一个goroutine可以创建多个不同的goroutine来处理子任务,这时候就形成了一棵goroutine调用关系树,而这些goroutine之间往往需要传递截止时间、取消信号或其他与请求有关的数据,这时候就可以使用context。

要达到的目的

  • 传递数据
  • 上层任务取消后,所有的下层任务都会被取消;
  • 中间某一层的任务取消后,只会将当前任务的下层任务取消,而不会影响上层的任务以及同级任务

Context的概要总结

一个接口、4种具体实现(或者说有4种类型)、还有6个函数

概念一:1个接口

Context定义是一个接口类型,所有的Ctx类型都需要实现这个接口的4个方法

 type Context interface {
  // 返回context何时会超时
  Deadline() (deadline time.Time, ok bool)

  // 在Context被取消或超时时返回一个close的channel,close的channel可以作为广播通知,告诉给context相关的函数要停止当前工作然后返回
  Done() <-chan struct{}

  // 方法返回context为什么被取消
  Err() error

  // 返回context相关的数据
  Value(key interface{}) interface{}
}  

概念二:4种实现(4中类型)

有四种类型的Context实现

第一种:emptyCtx

理解:emptyCtx本质上是个整形,emptyCtx对Context接口的实现,只是简单的返回nil、false等等,实际上什么也没做。所以emptyCtx用来作为context树的根节点

创建方法:Background跟TODO这两个函数内部都会创建emptyCtx

定义:

 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
}  

第二种:cancelCtx

理解:cancelCtx类型,是一种可取消的Context。

创建方法:WithCancel

定义:

 type cancelCtx struct {
   Context

   mu       sync.Mutex            // 用来保护这几个字段的锁,以保证cancelCtx是线程安全的
   done     chan struct{}         // 用于获取该context的取消通知
   children map[canceler]struct{} // 用于存储以当前节点为根节点的所有可取消的context,以便在跟节点取消时,可以把他们一并取消
   err      error                 // 用于存储取消时指定的错误信息
}  

第三种:timerCtx

理解:timerCtx在cancelCtx的基础上又封装了一个定时器和一个截止时间,这样既可以根据需要主动取消,也可以在到达deadline时通过timer来触发取消动作。要注意这个timer也会由cancelCtx.mu来保护,确保取消操作也是线程安全的

创建方法:WithTimeout跟WithDeadline这两个函数

定义:

 type timerCtx struct {
  cancelCtx
  
  timer *time.Timer // Under cancelCtx.mu.
  deadline time.Time
}  

第四种:valueCtx

理解: 用来支持键值对打包,WithValue函数可以给Context附加一个键值对信息,然后就可以通过Context传递数据了

创建方法:WithValue

定义:

 type valueCtx struct {
  Context
  key, val interface{}
}  

概念三:6个方法

创建4中context示例的6个函数


函数名

函数

用处

Background

func Background() Context

用于创建emptyCtx类型的Context。特点:作为Context树的根节点

TODO

func TODO() Context

用于创建emptyCtx类型的Context。特点:官方文档建议在本来应该使用外层传递的ctx,而外层却没有传递的地方使用,就像函数名称表达的含义一样,留下一个TODO

WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

用于创建cancelCtx类型的Context

WithTimeout

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

用于创建timerCtx类型的Context。特点:WithTimeout函数接收一个时间段

WithDeadline

WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

用于创建timerCtx类型的Context。特点:WithDeadline函数需要指定一个时间点

WithValue

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

用于创建valueCtx类型的Context

示例1:

 package main

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

// parentGoroutine 父G
func parentGoroutine(ctx context.Context) {
   for {
      select {
      case <-ctx.Done():
         fmt.Println("收到信号,父ctx err=", ctx.Err())
         return
      default:
         fmt.Println("父ctx监控中")
         time.Sleep(1 * time.Second)
      }
   }
}

// childrenGoroutine 子G
func childrenGoroutine(ctx context.Context) {
   for {
      select {
      case <-ctx.Done():
         fmt.Println("收到信号,子ctx err=", ctx.Err())
         return
      default:
         fmt.Println("子ctx监控中")
         time.Sleep(1 * time.Second)
      }
   }
}

func main() {
   // 创建一个根节点
   ctx := context.Background()

   // 可退出的Context
   parentCtx, cancelFunc := context.WithCancel(ctx)
   // 监控parentCtx 的 done 通知
   go parentGoroutine(parentCtx)

   timerChildrenCtx, _ := context.WithCancel(parentCtx) // 收到父G信号,退出
   // 监控childrenCtx 的 done 通知
   go childrenGoroutine(timerChildrenCtx)

   cancelFunc() // 通知parentCtx,退出

   time.Sleep(3 * time.Second)
}  

输出结果:

 收到信号,父ctx err= context canceled
收到信号,子ctx err= context canceled  

示例2:

 package main

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

// parentGoroutine 父G
func parentGoroutine(ctx context.Context) {
  for {
    select {
    case <-ctx.Done():
      fmt.Println("收到信号,父ctx err=", ctx.Err())
      return
    default:
      fmt.Println("父ctx监控中")
      time.Sleep(1 * time.Second)
    }
  }
}

// childrenGoroutine 子G
func childrenGoroutine(ctx context.Context) {
  for {
    select {
    case <-ctx.Done():
      fmt.Println("收到信号,子ctx err=", ctx.Err())
      return
    default:
      fmt.Println("子ctx监控中")
      time.Sleep(1 * time.Second)
    }
  }
}

func main() {
  // 创建一个根节点
  ctx := context.Background()

  // 可退出的Context
  parentCtx, _ := context.WithTimeout(ctx, 3*time.Second) // 超时退出
  // 监控parentCtx 的 done 通知
  go parentGoroutine(parentCtx)

  timerChildrenCtx, _ := context.WithCancel(parentCtx) // 收到父G信号,退出
  // 监控childrenCtx 的 done 通知
  go childrenGoroutine(timerChildrenCtx)

  time.Sleep(6 * time.Second)
}
  

输出结果:

 父ctx监控中
父ctx监控中
子ctx监控中
父ctx监控中
子ctx监控中
收到信号,子ctx err= context deadline exceeded
收到信号,父ctx err= context deadline exceeded
  

相关文章