Explicación detallada de la función y el uso de Contexto en el lenguaje Go

KDP (plataforma de servicio de datos) es un producto de servicio de datos desarrollado de forma independiente por KaiwuDB. Con KaiwuDB como núcleo, es una plataforma de servicio de datos integral para escenarios de AIoT, que cumple con la Internet industrial de las cosas, la energía digital, la Internet de los vehículos, las industrias inteligentes. y otras industrias Requisitos comerciales integrales para la recopilación, el procesamiento, el cálculo, el análisis y la aplicación de datos en escenarios comerciales centrales, al darse cuenta de que "el negocio son datos, los datos son servicios" y ayudar a las empresas a extraer un mayor valor comercial de los datos.

Al desarrollar el componente informático en tiempo real de la plataforma de servicios de datos, es posible que encuentre un problema de este tipo: el componente informático en tiempo real proporciona a los usuarios la función de reglas personalizadas. Después de que el usuario registra varias reglas y las ejecuta durante un período de tiempo , y luego modifica la definición de las reglas y reinicia, se producirá el problema. Ocurrió una fuga de rutina.

1. Caso real

Este artículo utilizará pseudocódigo para presentar de manera integral el problema de la fuga de corrutina que se puede encontrar en el proceso de desarrollo de componentes informáticos en tiempo real de la plataforma de servicios de datos.

//规则的大致数据结构
type DummyRule struct{
    BaseRule    
    sorce []Source    
    sink  []Sink    
    //flow map key:flow 名称,value:flow 实例    
    flow map[string]Flow    
    ...
}

La DummyRule anterior es la estructura de datos de la regla de este ejemplo, que incluye varios orígenes de datos Source, varios destinos de datos Sinks y flujo de datos Flow. El proceso específico de las reglas es el siguiente:

1 y 2 son dos fuentes, primero procese las dos fuentes de 1 y 2 respectivamente por adición; en segundo lugar, llame a la operación Merge para sintetizar un flujo; luego realice la operación Fanout para generar dos flujos idénticos, que desembocan en 7 y 8 respectivamente; finalmente pase 7 y El tipo de número 8 se convierte en una cadena de caracteres y se escribe en los archivos out1.txt y out2.txt respectivamente.

type Source struct{
  consumers       []file.reader  
  out             chan interface{}  
  ctx             context.Context  
  cancel          context.CancelFunc  
  ...
}

La figura anterior es el pseudocódigo de la fuente de datos de la clase Source, los consumidores son los lectores que se usan para leer los datos del archivo, out es el canal que se usa para pasar al siguiente flujo de datos y ctx es el contexto de Go. Es una rutina separada para que los consumidores lean los datos del archivo, y los datos leídos se pondrán a la espera del consumo del siguiente flujo de datos.

type Sink struct{
   producers  []file.writer   
   in         chan interface{}   
   ctx        context.Context   
   cancel context.CancelFunc   
   ...
}

La figura anterior es el pseudocódigo del objeto de datos de clase Sink, los productores son los escritores que se usan para escribir archivos, in es el canal que se usa para aceptar el flujo de datos anterior, ctx es el contexto de Go, los productores escriben datos de archivos también rutina

func(fm FlatMap) Via(flow streams.Flow) streams.Flow{
    go fm.transmit(flow)
    return flow
}

La figura de arriba es el código fuente de la transferencia de flujo de datos. El uso de FlatMap es curFlow := prevFlow.Via(nextFlow), de forma que se pueda pasar el Flow anterior al siguiente Flow, se puede ver que se realiza un proceso de transferencia de flujo de datos en una corrutina.

Del código fuente anterior, podemos ver que hay al menos 10 corrutinas en esta regla de ejemplo, pero de hecho, hay muchas más de 10 corrutinas. Se puede observar que en los componentes de computación en tiempo real de la plataforma de servicios de datos, la gestión de corrutinas es muy complicada.

Después de repetidas pruebas e investigaciones con herramientas como go pprof, top y go traces, descubrimos que la fuga de corrutina se debió a la cancelación incorrecta del contexto del fregadero en las reglas.

El contexto es una característica importante del lenguaje para administrar las rutinas. Aprender a usar Context correctamente puede aclarar y administrar mejor la relación entre rutinas. De los ejemplos anteriores, podemos ver la importancia del Contexto.Aprender a usar el Contexto correctamente no solo puede mejorar la calidad del código, sino también evitar una gran cantidad de trabajo de investigación de fugas de corrutina.

dos, en el contexto

1. Introducción

El contexto generalmente se llama contexto. En lenguaje Go, se entiende como el estado de ejecución y la escena de la rutina. Hay una transferencia de Contexto entre las rutinas superiores e inferiores, y las rutinas superiores pasarán el Contexto a las rutinas inferiores.

Antes de que se ejecute cada goroutine, necesita conocer el estado de ejecución actual del programa por adelantado. Por lo general, estos estados se encapsulan en una variable de contexto y se pasan a la goroutine para que se ejecute.

En la programación de red, cuando se recibe una Solicitud de solicitud de red y se procesa la Solicitud, se puede procesar en varias rutinas. Y es posible que estas rutinas tengan que compartir alguna información de la Solicitud. Cuando la Solicitud se cancela o se agota el tiempo de espera, todas las rutinas creadas a partir de esta Solicitud también se cancelarán.

El paquete Go Context no solo implementa el método de compartir variables de estado entre unidades de programa, sino que también puede pasar señales como caducidad o revocación al programa llamado estableciendo el valor de la variable ctx fuera de la unidad de programa llamada de una manera sencilla.

En la programación de red, si A llama a la API de B y B llama a la API de C, entonces si A llama a B para cancelar, entonces la llamada de B a C también debe cancelarse. El paquete Context hace que sea muy conveniente pasar datos de solicitud, señales de cancelación e información de tiempo de espera entre rutinas de solicitud.

El núcleo del paquete Context es la interfaz Context:

// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface{     
     // 返回一个超时时间,到期则取消context。在代码中,可以通过deadline为io操作设置超过时间     
     Deadline() (deadline time.Time, ok bool)     
     // 返回一个channel,用于接收context的取消或者deadline信号。     
     // 当channel关闭,监听done信号的函数会立即放弃当前正在执行的操作并返回。     
     // 如果context实例时不可能取消的,那么     
     // 返回nil,比如空context,valueCtx     
     Done()
}

2. Cómo usar

Para goroutines, su relación de creación y llamada siempre es como una llamada capa por capa, como una estructura de árbol, y el Contexto en la parte superior debe tener una forma de cerrar activamente la ejecución de goroutines subordinados. Para realizar esta relación, Context también es una estructura de árbol, y los nodos hoja siempre se derivan del nodo raíz.

Para crear un árbol de contexto, el primer paso debe ser obtener el nodo raíz y el valor de retorno de la función Context.Backupgroup es el nodo raíz.

func Background() Context{
    return background
}

Esta función devuelve un Contexto vacío, que generalmente es creado por la primera gorutina que recibe la solicitud y es el nodo raíz del Contexto correspondiente a la solicitud entrante, no se puede cancelar, no tiene valor y no tiene tiempo de caducidad. A menudo existe como el Contexto de nivel superior que maneja la Solicitud.

Con el nodo raíz se pueden crear nodos descendientes, el paquete Context proporciona una serie de métodos para crearlos:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {}
func WithDeadline(parent Context, d time.Time)(Context, CancelFunc) {}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {}
func WithValue(parent Context, key, val interface{}) Context {}

La función recibe un padre de tipo Contexto y devuelve un valor de tipo Contexto, por lo que se crean diferentes Contextos capa por capa, el nodo hijo se obtiene copiando el nodo padre, y algunos valores de estado del nodo hijo son establecer de acuerdo con los parámetros recibidos, y luego el nodo secundario se puede pasar a la rutina inferior.

¿Cómo pasar el estado cambiado a través de Contexto?

En la rutina principal, se puede obtener un método de cancelación a través del método Withxx, obteniendo así el derecho de operar el Contexto secundario.

(1)ConCancelar

 La función WithCancel es copiar el nodo principal al nodo secundario y devolver una variable de tipo de función CancelFunc adicional, que se define como: type CancelFunc func()

Llamar a CancelFunc cancelará el objeto contextual secundario correspondiente. En la rutina principal, el contexto del nodo secundario se puede crear a través de WithCancel, y también se obtiene el control de la rutina secundaria. Una vez que se ejecuta la función CancelFunc, el contexto del nodo secundario finaliza. El nodo secundario necesita el siguiente código para determine si ha terminado y salga de goroutine:

select {
case <- ctx.Cone():
    fmt.Println("do some clean work ...... ")
}

(2) Con fecha límite

 La función de WithDeadline es similar a la de WithCancel. También copia el nodo padre al nodo hijo, pero su tiempo de expiración está determinado por el tiempo de expiración de la fecha límite y el padre. Cuando la hora de vencimiento del padre es anterior a la fecha límite, la hora de vencimiento devuelta es la misma que la hora de vencimiento del padre. Cuando el nodo principal caduca, todos los nodos descendientes deben cerrarse al mismo tiempo.

(3) Con tiempo de espera

La función WithTimeout es similar a WithDeadline, excepto que lo que pasa es el tiempo de vida restante del contexto a partir de ahora. Ambos también devuelven el control del contexto secundario creado, una variable de función de tipo CancelFunc.

Cuando finaliza la función de solicitud de solicitud de nivel superior, podemos cancelar un determinado Contexto, y la goroutine descendiente juzga el final de acuerdo con select ctx.Done().

(4)Con valor

Con la función Valor, devuelve una copia del elemento principal, llamar al método Valor (clave) de esta copia obtendrá el valor. De esta forma, no solo conservamos el valor original del nodo raíz, sino que también agregamos nuevos valores a los nodos descendientes; tenga en cuenta que si existe la misma clave, se sobrescribirá.

3. Ejemplos

package main
import (
        "context"        
        "fmt"        
        "time"
)
func main() {
        ctxWithCancel, cancel := context.WithTimeout(context.Background(), 5 * time.Second)                
        
        go worker(ctxWithCancel, "[1]")        
        go worker(ctxWithCancel, "[2]")                
        
        go manager(cancel)                
        
        <-ctxWithCancel.Done()        
        // 暂停1秒便于协程的打印输出        
        time.Sleep(1 * time.Second)        
        fmt.Println("example closed")
}
func manager(cancel func( )) {
        time.Sleep(10 * time.Second)         
        fmt.Println("manager called cancel()")         
        cancel() 
}                
func worker(ctxWithCancle context.Context, name string) {
        for {
                 select {                 
                 case <- ctxWithCancel.Done():                          
                          fmt.Println(name, "return for ctxWithCancel.Done()")                          
                          return                 
                 default:
                          fmt.Println(name, "working")                 
                          }                 
                          time.Sleep(1 * time.Second)        
        }
}

El diagrama de arquitectura del Contexto de este proceso:

[1]working
[2]working
[2]working
[1]working
[1]working
[2]working
[2]working
[1]working
[1]working
[2]working
[1]return for ctxWithCancel.Done()
[2]return for ctxWithCancel.Done()example closed

Se puede ver que el final del trabajador esta vez es causado por la expiración del temporizador de ctxWithCancel.

Cambie la duración del administrador a 2 segundos, mantenga la duración de WithTimeout sin cambios y vuelva a ejecutarlo. El trabajador solo trabajó durante 2 segundos antes de que el administrador lo detuviera por adelantado.

[1]working
[2]working
[2]working
[1]workingmanager called cancel()
[1]return for ctxWithCancel.Done()
[2]return for ctxWithCancel.Done()example closed

Supongo que te gusta

Origin blog.csdn.net/ZNBase/article/details/131410274
Recomendado
Clasificación