Introducción al contexto de Golang y análisis del código fuente

Introduccion

En el servicio Go, para cada solicitud, se procesará una rutina. En el proceso de las rutinas, muchas de ellas también se utilizan para acceder a recursos, como bases de datos, como RPC, que también necesitan acceder a cierta información de la dimensión de la solicitud, como la identidad del solicitante, la información de autorización, etc. Cuando se cancela una solicitud o se agota el tiempo de espera, todas las demás rutinas deben cancelarse inmediatamente para liberar recursos.
El paquete de contexto de Golang se usa para transferir los datos, las señales y el tiempo de espera de la dimensión solicitada a todas las rutinas que procesan la solicitud. En la cadena de llamadas al método que procesa la solicitud, se debe pasar el contexto. Por supuesto, también puede usar WithCancel, WithDeadline, WithTimeout o WithValue para derivar un contexto secundario para pasar. Cuando se cancela un contexto, se cancelarán todos los contextos secundarios derivados de él.
Introducción del paquete.

Contexto

En el paquete de contexto, el núcleo es la interfaz de contexto, que tiene la siguiente estructura:

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

Un contexto puede pasar una fecha límite, una señal de cancelación y otros valores. El método puede ser llamado por múltiples corutinas al mismo tiempo.

  • Deadline () (hora límite. Time, ok bool)
    devuelve una fecha límite, lo que significa que el contexto se cancelará si llega a la fecha límite. Si el ok devuelto es falso, no se ha establecido un tiempo de corte, es decir, no se agotará el tiempo de espera.

  • Listo () <-chan struct {}
    devuelve un canal cerrado, lo que indica que el contexto ha sido cancelado. Si el contexto nunca se puede cancelar, devuelva nil.
    Generalmente Listo se usa en la instrucción select, como:

func Stream(ctx context.Context, out chan<- Value) error {//Stream函数就是用来不断产生值并把值发送到out channel里,直到发生DoSomething发生错误或者ctx.Done关闭
   for {
      v, err := DoSomething(ctx)//DoSomething用来产生值
      if err != nil {
         return err
      }
      select {
      case <-ctx.Done(): //ctx.Done关闭
         return ctx.Err()
      case out <- v://将产生的值发送出去
      }
   }
}

  • El error devuelto por el error Err () se usa para explicar el motivo de la cancelación de este contexto. Si el canal Listo no se ha cerrado, Err () devuelve nulo.
    Los dos errores que vienen con el paquete general son: cancelación y tiempo de espera
var Canceled = errors.New("context canceled")
var DeadlineExceeded error = deadlineExceededError{}
  • Value (key interface {}) interface {}
    devuelve el valor correspondiente a la clave almacenada en el contexto, o nulo si no existe.

Con Cancelar

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
Copie el contexto principal y asigne un nuevo canal Listo. Devuelve este contexto y cancela la función.
Cuando ocurre uno de los siguientes, el contexto se cancelará:

  • la función cancelar se llama
  • El canal Listo del contexto primario está cerrado,
    por lo que al escribir código, si el procesamiento lógico se ha completado en el contexto actual, se debe llamar a la función de cancelación para notificar a otras corutinas para liberar recursos.
    Ejemplos de uso:
func main() {
   //gen函数用来产生一个不断生产数字到channel的协程,并返回channel
   gen := func(ctx context.Context) <-chan int {
      dst := make(chan int)
      n := 1
      go func() {
         for {
            select {
            case <-ctx.Done():
               return //每一次生产数字都检查context是否已经被取消,防止协程泄露
            case dst <- n:
               n++
            }
         }
      }()
      return dst
   }

   ctx, cancel := context.WithCancel(context.Background())
   defer cancel() //在消费完生产的数字之后,调用cancel函数来取消context,以此通知其他协程

   for n := range gen(ctx) { //只消费5个数字
      fmt.Println(n)
      if n == 5 {
         break
      }
   }
}

Análisis de código fuente

¿Cómo se implementa este método? Echemos un vistazo al código fuente:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
   c := newCancelCtx(parent)
   propagateCancel(parent, &c)
   return &c, func() { c.cancel(true, Canceled) }
}

Se puede ver que existen principalmente dos métodos: newCancelCtx y propagateCancel
newCancelCtx consiste en inicializar un objeto cancelCtx con el contexto principal, y cancelCtx es una clase de contexto de implementación:

func newCancelCtx(parent Context) cancelCtx {
   return cancelCtx{Context: parent}
}

Mirando la estructura de este newCancelCtx, newCancelCtx es en realidad una clase de implementación de la interfaz Context:

type cancelCtx struct {
   Context //指向父context的引用

   mu       sync.Mutex            // 锁用来保护下面几个字段
   done     chan struct{}         // 用来通知其他,代表这个context已经结束了
   children map[canceler]struct{} // 里面保存了所有子contex的联系,用来在结束当前context的时候,结束所有子context。这个字段cancel之后,设为nil。
   err      error                 // cancel之后,就不是nil了。
}

Como su nombre lo indica, propagateCancel es para propagar cancelar, lo que es para garantizar que cuando el contexto primario finalice, el contexto secundario que obtengamos con WithCancel pueda seguir el final.
Luego regrese al objeto cancelCtx recién creado y a CancelFunc. CancelFunc es una función que internamente llama al método cancel del objeto cancelCtx. La función de este método es cerrar el canal hecho del objeto cancelCtx (que representa el final del contexto), y luego cancelar todos los contextos secundarios del contexto, y cortar el contexto si es necesario. La relación con su contexto primario (de hecho, el contexto secundario de este contexto está asociado con propagateCancel).
Echa un vistazo al interior de propagateCancel:

func propagateCancel(parent Context, child canceler) {
   if parent.Done() == nil {//父context如果永远不能取消,直接返回,不用关联。
      return
   }
   if p, ok := parentCancelCtx(parent); ok {//因为传入的父context类型是Context接口,不一定是CancelCtx,所以如果要关联,则先判断类型
      p.mu.Lock()//加锁,保护字段children
      if p.err != nil {//说明父context已经结束了,也别做其他操作了,直接取消这个子context吧
         child.cancel(false, p.err)
      } else {//没有结束,就在父context里加上子context的联系,用来之后取消子context用
         if p.children == nil {
            p.children = make(map[canceler]struct{})
         }
         p.children[child] = struct{}{}
      }
      p.mu.Unlock()
   } else {//因为传入的父context类型不是CancelCtx,则不一定有children字段的,只能起一个协程来监听父context的Done,如果Done关闭了,就可以取消子context了。
      go func() {
         select {
         case <-parent.Done():
            child.cancel(false, parent.Err())
         case <-child.Done()://为了避免子context比父context先取消,造成这个监听协程泄露,这里加了这样一个case
         }
      }()
   }
}

Echemos un vistazo a lo que hace exactamente c.cancel (verdadero, cancelado) en la devolución de WithCancel & c, func () {c.cancel (verdadero, cancelado)}:
porque c es de tipo cancelCtx, hay un método de cancelación Este método es en realidad un método para implementar la interfaz de cancelación. En el campo child map [canceler] struct {} del campo cancelCtx anterior, puede ver que la clave de este mapa es esta interfaz.

type canceler interface {
   cancel(removeFromParent bool, err error)
   Done() <-chan struct{}
}

Esta interfaz contiene dos métodos: las clases de implementación son * cancelCtx y timerCtx.
Mira
para lograr cancelCtx, el papel de este método es apagar cancelCtx objetos canal de hacerlo (en nombre de la final de este contexto), entonces todos los sub-contexto cancelar este contexto, si es necesario, y cortar esta relación y su contexto padre del contexto (de hecho, El contexto secundario de este contexto está asociado con propagateCancel):

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
   if err == nil {//任何context关闭后,都需要一个错误来给字段err赋值来表明结束的原因,不传入err是不行的
      panic("context: internal error: missing cancel error")
   }
   c.mu.Lock()//加锁
   if c.err != nil {//如果错误已经有了,说明这个context已经结束了,就不用cancel了
      c.mu.Unlock()
      return // already canceled
   }
   c.err = err//赋值错误原因
   if c.done == nil {
      c.done = closedchan //这个字段延迟加载,closedchan是一个context包中的量,一个已经关闭的channel,所有context都复用这个关闭的channel
   } else {//当然如果已经加载了,则直接关闭。那是啥时候加载的呢?当然是在这个context还没结束的时候,有人调用了Done()方法,所以是延迟加载。
      close(c.done)
   }
   for child := range c.children {//结束所有子context。之前每次的propagateCancel总算派上用场了。
      child.cancel(false, err)
   }
   c.children = nil
   c.mu.Unlock()

   if removeFromParent {//如果需要的话,切断当前context和父context的联系。就是从父context的children  map里移除嘛。当然如果fucontext不是cancelCtx,就没事咯
      removeChild(c.Context, c)
   }
}

Esto introduce el código fuente y se ve bien.

WithDeadline

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
Copie el contexto principal y establezca la fecha límite en d. Si el tiempo de corte del contexto padre es menor que d, entonces se usa el tiempo de corte del contexto padre.
Cuando ocurre uno de los siguientes, el contexto se cancelará:

  • Fecha límite
  • Devuelve la función de cancelación para que se llame
  • El contexto de los padres hace canal está cerrado
    ejemplos usos
func main() {
   d := time.Now().Add(50 * time.Millisecond)
   ctx, cancel := context.WithDeadline(context.Background(), d)

   //即使ctx已经设置了截止时间,会自动过期,但是最好还是在不需要的时候主动调用cancel函数
   defer cancel()

   select {
   case <-time.After(1 * time.Second):
      fmt.Println("overslept")
   case <-ctx.Done():     //这个会先到达
      fmt.Println(ctx.Err())
   }
}

Análisis de código fuente

Eche un vistazo a la implementación de WithDeadline:

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
   if cur, ok := parent.Deadline(); ok && cur.Before(d) {//如果当前的Deadline比父Deadline晚,则用父Deadline,直接WithCancel就好了,因为父Deadline到了结束了,这个context也就结束了。WithCancel是为了返回一个cancelfunc。
      return WithCancel(parent)
   }
   c := &timerCtx{//可以看到,timerCtx其实就是包装了一下newCancelCtx,newCancelCtx在前文已经介绍了,这里看上去就简单多了。
      cancelCtx: newCancelCtx(parent),
      deadline:  d,
   }
   propagateCancel(parent, c)//propagateCancel在前文介绍过了,这里就是传播cancel嘛。
   dur := time.Until(d)
   if dur <= 0 {//时间到了,就直接可以cancel了
      c.cancel(true, DeadlineExceeded)
      return c, func() { c.cancel(false, Canceled) }
   }
   c.mu.Lock()
   defer c.mu.Unlock()
   if c.err == nil {//如果还没取消,就设置个定时器,AfterFunc函数就是说,在时间dur之后,执行func,即cancel。
      c.timer = time.AfterFunc(dur, func() {
         c.cancel(true, DeadlineExceeded)
      })
   }
   return c, func() { c.cancel(true, Canceled) }
}

Comprenda la implementación de WithCancel, el código fuente de este WithCancel sigue siendo muy simple.

WithTimeout

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
Después de un cierto período de tiempo de espera, cancele automáticamente el contexto y su contexto hijo.
De hecho, se utiliza WithDeadline, pero la fecha límite se escribe en el tiempo actual + tiempo de espera

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
   return WithDeadline(parent, time.Now().Add(timeout))
}

El ejemplo de uso es el mismo.

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())
   }

}

Análisis de código fuente

Solo mira el código fuente de WithDeadline.

WithValue

func WithValue(parent Context, key, val interface{}) Context
Copie el contexto principal y establezca el valor clave en el contexto. De esta manera, los datos se pueden sacar del contexto y utilizar.
Cabe señalar que el valor del contexto se utiliza para transferir los datos de la dimensión solicitada, no los parámetros opcionales de la función.
La clave utilizada para transferir datos no debe ser una cadena ni ningún otro tipo de Go integrado, sino que debe utilizar un tipo definido por el usuario como clave. Esto evitará conflictos.
La clave debe ser comparable, lo que significa que se puede usar para determinar si es la misma clave, es decir, igual.
El tipo estático de la clave de contexto exportada debe usar punteros o
ejemplos de uso de la interfaz

func main() {
   type favContextKey string //定义一个自定义类型作为key

   f := func(ctx context.Context, k interface{}) {//判断key是否存在以及值的函数
      if v := ctx.Value(k); v != nil {
         fmt.Println("found value:", v)
         return
      }
      fmt.Println("key not found:", k)
   }

   k := favContextKey("language")
   ctx := context.WithValue(context.Background(), k, "Go") //使用自定义类型作为key

   f(ctx, k) //found value: Go
   f(ctx, favContextKey("color")) //key not found: color

   ctx2 := context.WithValue(ctx, "language", "Java") //使用string,试图覆盖之前的key对应的值
   f(ctx2, k) //found value: Go ,并没有被覆盖
   f(ctx2, "language") //found value: Java ,两个key互相独立
}

Cuando se utiliza el contexto para almacenar el valor clave, la mejor manera es no exportar la clave, solo se puede acceder a la clave en el paquete, definido en el paquete, y luego el paquete proporciona un método de acceso seguro para guardar el valor clave y tomar la clave- valor Tales como:

type User struct {// User是我们要作为value保存在contex里的值
   //自定义字段
}

type key int //key是我们定义在包内的key类型,这样不会与其他包的冲突

var userKey key//key类型的变量,用作context里的key。

// NewContext方法用来将value存入context
func NewContext(ctx context.Context, u *User) context.Context {
   return context.WithValue(ctx, userKey, u)
}
// FromContext用来取value
func FromContext(ctx context.Context) (*User, bool) {
   u, ok := ctx.Value(userKey).(*User)
   return u, ok
}

Análisis de código fuente

Eche un vistazo a la implementación de WithValue:

func WithValue(parent Context, key, val interface{}) Context {
   if key == nil {//没有key肯定是不行的辣
      panic("nil key")
   }
   if !reflectlite.TypeOf(key).Comparable() {//key不可比较也是不行的辣,Comparable()是接口Type的一个方法,不可比较,那么取value的时候,咋知道你到底想取啥
      panic("key is not comparable")
   }
   return &valueCtx{parent, key, val}
}

Al ver que se devuelve un objeto valueCtx, qué es esto, Kang Kang:

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

Buen tipo, resultó ser un contexto, más dos campos clave y valor. Esto está bien, entonces si hay más WithValue varias veces, entonces no debe ser una cadena larga. Se puede ver que cada WithValue aquí, hay un nodo más. Esta cadena puede ser lo suficientemente larga. Tomar el valor es atravesar la cadena de contexto hacia arriba. Implementemos el método Value () de Kangkang. De hecho, sabemos que Value (key interface {}) interface {} es un método de la interfaz de contexto, por lo que consideramos esta implementación en la estructura valueCtx:

func (c *valueCtx) Value(key interface{}) interface{} {
   if c.key == key {//key的可比性就是用在这里的
      return c.val
   }
   return c.Context.Value(key)//如果当前valueCtx里没有找到这个key,就向上遍历链,直到找到为止
}

Si la cadena ha sido atravesada hacia arriba, si no puede encontrar la clave, terminará en el contexto superior: fondo o todo para
verla:

var (
   background = new(emptyCtx)
   todo       = new(emptyCtx)
)
func Background() Context {
   return background
}
func TODO() Context {
   return todo
}

Sí, context.Background () y context.TODO () crean tal cosa, un objeto de tipo emptyCtx, lo que es emptyCtx, para interceptar una pequeña parte de Kang Kang:

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
}

El buen chico es un engaño aterrador, esencialmente un int, y en realidad implementa la interfaz de contexto.
Su valor devuelve nulo y el caso está resuelto.

Antecedentes

func Background() Context
Devuelve un contexto no nulo y no vacío que nunca se cancelará, no tiene valor y no tiene fecha límite. Generalmente se usa en la función principal o inicialización, se usa como contexto de nivel superior.

TODO

func TODO() Context
Devuelve un contexto no nulo no vacío. Puede usar esto cuando no sabe qué contexto usar, o si aún no usa el contexto pero tiene este parámetro de entrada.

Supongo que te gusta

Origin www.cnblogs.com/chaunceeeeey/p/12740325.html
Recomendado
Clasificación