Detalles del canal del canal

contenido

1. Introducción al canal

1. Declarar el canal

2. Crea un canal

2. Operación del canal

1. Enviar

2. Recibir

3. Cerrar

3. Canal sin búfer

4. Canal amortiguado

5. Canal de lectura cíclica

6. Cierra el canal

Siete, canal unidireccional


1. Introducción al canal

No tiene sentido simplemente ejecutar funciones al mismo tiempo. Los datos deben intercambiarse entre funciones para reflejar el significado de la ejecución simultánea de funciones.

Aunque la memoria compartida se puede usar para el intercambio de datos, la memoria compartida es propensa a condiciones de carrera entre diferentes rutinas. Para garantizar la corrección del intercambio de datos, la memoria debe estar bloqueada con un mutex, lo que seguramente causará problemas de rendimiento.

        Go aboga por el uso de métodos de comunicación en lugar de memoria compartida, el método de comunicación aquí es usar canales, como se muestra en la siguiente figura.

        Cuando hay mucha gente en lugares públicos como estaciones de metro, comedores y baños, todo el mundo ha desarrollado el hábito de hacer cola, con el fin de evitar el proceso ineficiente de uso e intercambio de recursos causado por la aglomeración y el corte de colas. Lo mismo es cierto para el código y los datos. Para competir por los datos, varias rutinas gor inevitablemente conducirán a la ineficiencia de la ejecución. La forma más eficiente de usar las colas es que un canal sea una estructura similar a una cola.

        Un canal en Go es un tipo especial. En cualquier momento, solo una gorutina puede acceder al canal para enviar y obtener datos. La comunicación entre goroutines es posible a través de canales.

Un canal es como una cinta transportadora o una cola, y siempre sigue la regla First In First Out (primero en entrar, primero en salir) para garantizar el orden de envío y recepción de datos.

 

1. Declarar el canal

var 变量 chan 元素类型

var ch1 chan int   // 声明一个传递整型的通道
var ch2 chan bool  // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道

El valor nulo del tipo chan es nil, que debe usarse con make after declaración.

Por lo tanto, el canal solo puede transmitir un tipo de datos, como chan int o chan string, todos los tipos se pueden usar para el canal y también se puede usar la interfaz de interfaz vacía{}. Incluso es posible (a veces muy útil) crear canales de canales.

2. Crea un canal

El canal es un tipo de referencia y necesita ser creado (memoria asignada) usando make. El formato es el siguiente:

var ch1 chan string
ch1 = make(chan string)

//或者使用短类型
ch1 := make(chan string)

Ejemplo

ch1 := make(chan int)                 //创建一个整型类型的通道
ch2 := make(chan interface{})         //创建一个空接口类型的通道, 可以存放任意格式

type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)             //创建Equip指针类型的通道, 可以存放*Equip

2. Operación del canal

Los canales tienen tres operaciones: enviar, recibir y cerrar. Tanto el envío como la recepción utilizan el símbolo <-.

Una vez que se crea un canal, se puede utilizar para operaciones de envío y recepción.

Defina un canal:

ch := make(chan int)

1. Enviar

Envía un valor al canal.

ch <- 10 // 把10发送到ch中

2. Recibir

Recibir valores de un canal.

x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果

3. Cerrar

Cerramos el canal llamando a la función de cierre incorporada

close(ch)

Lo que hay que tener en cuenta sobre el cierre del canal es que el canal debe cerrarse solo cuando se notifica a la rutina del receptor que se han enviado todos los datos.

El canal puede ser reclamado por el mecanismo de recolección de elementos no utilizados. Es diferente de cerrar el archivo. Es necesario cerrar el archivo después de la operación final, pero cerrar el canal no es necesario.

El canal cerrado tiene las siguientes características:

  • Enviar un valor a un canal cerrado provocará pánico.
  • Recibir en un canal cerrado seguirá obteniendo el valor hasta que el canal esté vacío. (si todavía hay datos en el canal)
  • Una operación de recepción en un canal que está cerrado y no tiene valor da como resultado un valor cero del tipo correspondiente.
  • Cerrar un canal ya cerrado generará pánico.

3. Canal sin búfer

 

canal sin búfer, también conocido como canal bloqueado

func main() {
    ch := make(chan int)
    ch <- 10
    fmt.Println("发送成功")
}

//这段代码仅作为 描述无缓冲通道,实际会形成deadlock
//具体原因,看下述分析

El código anterior se compila, pero cuando se ejecuta, se produce el siguiente error:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
      main.go:8 +0x54

El código anterior bloqueará la línea de código ch <- 10 para formar un interbloqueo, entonces, ¿cómo resolver este problema?

Una forma es iniciar una gorutina para recibir el valor, por ejemplo:

func recv(c chan int) {
    ret := <-c
    fmt.Println("接收成功", ret)
}

func main() {
    ch := make(chan int)
    go recv(ch) // 启用goroutine从通道接收值
    ch <- 10
    fmt.Println("发送成功")
}

Debido a que creamos un canal sin búfer con ch := make(chan int), un canal sin búfer solo puede enviar un valor cuando alguien lo recibe.

Resumen de canales sin búfer:

1) Una operación de envío en un canal sin búfer se bloqueará hasta que otra gorutina realice una operación de recepción en el canal, momento en el cual el valor se puede enviar con éxito y ambas gorutinas continuarán ejecutándose.

2) Por el contrario, si la operación de recepción se realiza primero, la gorutina del receptor se bloqueará hasta que otra gorutina envíe un valor al canal.

3) La comunicación que utiliza un canal sin búfer hará que las rutinas de envío y recepción se sincronicen. Por lo tanto, un canal sin búfer también se denomina canal síncrono.

4. Canal amortiguado

Otra forma de resolver el problema anterior es utilizar un canal con búfer.

 

Podemos especificar la capacidad del canal al inicializar el canal con la función make

1. Declaración de canal con búfer

通道实例 := make(chan 通道类型, 缓冲大小)

func main() {
    ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
    ch <- 10
    fmt.Println("发送成功")
}

Siempre que la capacidad de un canal sea mayor que cero, el canal es un canal con búfer y la capacidad del canal representa la cantidad de elementos que se pueden almacenar en el canal.

2. Condición de bloqueo

Los canales con búfer son similares en muchas características a los canales sin búfer. Un canal sin búfer se puede considerar como un canal con búfer cuya longitud siempre es cero. Por lo tanto, de acuerdo con esta función, el canal almacenado en el búfer aún se bloqueará en los siguientes casos:

  • Cuando el canal almacenado en búfer está lleno, se bloquea al intentar enviar datos nuevamente.
  • Bloqueo al intentar recibir datos cuando el canal almacenado en búfer está vacío.

¿Por qué limitar la longitud del canal y no proporcionar el canal de longitud infinita?

Sabemos que un canal es un puente de comunicación entre dos rutinas. El código que usa goroutines debe tener una parte que proporcione datos y una parte que consuma datos. Cuando la velocidad de suministro de datos del proveedor de datos es más rápida que la velocidad de procesamiento de datos del consumidor, si el canal no limita la longitud, la memoria continuará expandiéndose hasta que la aplicación falle.

Por lo tanto, limitar la longitud del canal es beneficioso para restringir la velocidad de suministro del proveedor de datos. La cantidad de datos suministrados debe estar dentro del rango del volumen de procesamiento del consumidor + la longitud del canal, para que los datos puedan ser procesado normalmente.

5. Canal de lectura cíclica

El código anterior es demasiado laborioso para leer los canales uno por uno.Go nos permite usar rangos para leer canales:

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3

    for v := range ch {
        fmt.Println(v)
    }
}

//deadline

Si ejecuta el código anterior, se informará un error de interbloqueo, porque el rango no terminará de leerse hasta que se cierre el canal. Es decir, si el canal del búfer se seca, el rango bloqueará la rutina actual, por lo que se bloqueará. Entonces, tratamos de evitar esta situación, es más fácil pensar en terminar la lectura cuando el canal está vacío.

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
for v := range ch {
    fmt.Println(v)
    if len(ch) <= 0 { // 如果现有数据量为0,跳出循环
        break
    }
}

El método anterior se puede generar normalmente, pero tenga en cuenta que el método de verificar el tamaño del canal no se puede usar para obtener todos los datos cuando se está accediendo al canal. En este ejemplo, solo almacenamos datos en ch, y ahora los buscamos uno por uno. ., el tamaño del canal está disminuyendo. Otra forma es cerrar el canal explícitamente:

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3

// 显式地关闭信道
close(ch)

for v := range ch {
    fmt.Println(v)
}

Un canal cerrado evitará el flujo de datos y es de solo lectura. Todavía podemos obtener datos del canal cerrado, pero ya no podemos escribir datos.

6. Cierra el canal

Puede cerrar el canal a través de la función integrada close() (si su canalización no almacena ni recupera valores, debe recordar cerrar el canal)

package main

import "fmt"

func main() {
    c := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        close(c)
    }()
    for {
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
    }
    fmt.Println("main结束")
}

¿Determinar si el canal está cerrado?

Cuando se envían datos limitados a través de un canal, podemos decirle a la gorutina que recibe valores del canal que deje de esperar cerrando el canal con la función de cierre.

Cuando se cierra un canal, el envío de un valor al canal provocará un pánico, y el valor recibido del canal siempre será de tipo cero. ¿Cómo juzgar si un canal está cerrado?

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    
    // 开启goroutine将0~100的数发送到ch1中
    go func() {
        for i := 0; i < 100; i++ {
            ch1 <- i
        }
        close(ch1)
    }()
    
    // 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
    go func() {
        for {
            
            // 通道关闭后再取值ok=false
            
            i, ok := <-ch1 
            if !ok {
                break
            }
            ch2 <- i * i
        }
        close(ch2)
    }()
    
    // 在主goroutine中从ch2中接收值打印
    for i := range ch2 { // 通道关闭后会退出for range循环
        fmt.Println(i)
    }
}

Descripción: después de conocer algunas condiciones de bloqueo del canal, para evitar el interbloqueo, puede usar una forma más amigable de leer los datos del canal

 if i, ok := <-ch1 ;ok{
     ...
 }

Siete, canal unidireccional

A veces, pasamos el canal como un parámetro entre múltiples funciones de tareas, y muchas veces usamos el canal en diferentes funciones de tareas para limitarlo, como restringir el canal para que solo envíe o solo reciba en la función.

var 通道实例 chan<- 元素类型    // 只能发送通道

var 通道实例 <-chan 元素类型    // 只能接收通道
//往通道中写
func counter(out chan<- int) {
    for i := 0; i < 100; i++ {
        out <- i
    }
    close(out)
}

func squarer(out chan<- int, in <-chan int) {
    for i := range in {
        out <- i * i
    }
    close(out)
}


//从通道中读
func printer(in <-chan int) {
    for i := range in {
        fmt.Println(i)
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go counter(ch1)
    go squarer(ch2, ch1)
    printer(ch2)
}

 

Supongo que te gusta

Origin blog.csdn.net/demored/article/details/124116140
Recomendado
Clasificación