Ir al canal de idioma (básico)

1. Introducción

  • El canal es un concepto muy importante en Go. Cooperar con goroutine es la clave para la implementación conveniente de la programación concurrente de Go. Hay dos mecanismos para lograr la concurrencia: el primero es el mecanismo de paso de mensajes, el segundo es el mecanismo de memoria compartida y el canal en Go utiliza el mecanismo de memoria compartida para cooperar con goroutine para lograr la concurrencia.
  • Esta publicación presenta los canales en Go de menos a más profundo, para que pueda revisarlos más tarde.

2. El uso del canal

2.1. Declarar y crear canal

2.1.1, canal bidireccional
  • El llamado canal bidireccional significa que puede escribir datos en el canal y leer datos del canal. El correspondiente es un canal unidireccional.
  • Declarar canal
var ch chan dataType
  • El tipo de datos es muy amplio, puede ser un tipo de datos básico, también puede ser un mapa, un segmento, un tipo de estructura personalizada o incluso un canal. Por lo tanto, es fácil compartir una variedad de datos a través de canales en Go.
  • Crear canal Hay dos formas, la primera es un canal sin búfer y la segunda es un canal con búfer. El método de creación es el siguiente:
//方式一:无缓冲
nobufferedch := make(chan int)
//方式二:带缓冲
bufferedch := make(chan int, 5)
2.1.2, canal unidireccional
  • El llamado canal unidireccional significa que los datos solo se pueden escribir en el canal, o que los datos solo se pueden leer desde el canal.
  • Declarar canal
//第一种:send-only channel
var singledirectch chan<- dataType
//第二种:receive-only channel
var singledirectch <-chan dataType
  • Crear canal También hay dos formas, la primera es un canal sin búfer y la segunda es un canal con búfer. El método de creación es el siguiente (tome el canal de solo envío como ejemplo):
//方式一:无缓冲
nobufferedch := make(chan<- int)
//方式二:带缓冲
bufferedch := make(chan<- int, 5)
  • Cuando usamos make para crear un canal, lo que realmente regresa es un puntero al canal.

2.2. Enviar y recibir datos en el canal (caso introductorio)

  • Un canal es como una canalización de mensajes. El remitente envía un mensaje en el extremo a de la canalización y el receptor recibe el mensaje en el extremo b de la canalización. Cuando la canalización de mensajes está llena de mensajes, el remitente no puede enviar mensajes a él y tiene que detenerse., Solo cuando el receptor recupera el mensaje de la canalización de mensajes y la canalización ya no está llena, el remitente que se detuvo antes puede continuar enviando mensajes a la canalización; de manera similar, cuando no hay ningún mensaje en la canalización, el El receptor tiene que detenerse. Deténgase, espere a que el remitente envíe el mensaje a la tubería, el receptor puede continuar recibiendo el mensaje en la tubería.
  • Siguiente con un clásicoProductor-consumidorModelo, que introduce el envío y la recepción de datos en el canal.
  • Aquí, el fabricante de bicicletas Producer es el remitente del mensaje, el consumidor que compró la bicicleta es el receptor del mensaje y la tienda de bicicletas Store es la canalización de mensajes, y luego esta es una canalización de mensajes en búfer con una capacidad de búfer de 5 Es decir, se pueden colocar un máximo de 5 bicicletas en la tienda a la vez, cuando haya 5 bicicletas en la tienda, el Productor se detendrá a descansar. No hay mucho que decir, solo ve al código.
package main

import (
	"fmt"
	"time"
)

type Bike struct {
    
    
	Id uint32
	Brand string
	Location string
}

var Store chan *Bike
var IsFinish chan bool

func init() {
    
     //初始化函数,先于 main 函数执行
	Store = make(chan *Bike,5)
	IsFinish = make(chan bool)
}

func Producer() {
    
     //自行车生产者
	for i := 0; i < 10; i++ {
    
     //总共生产 10 辆自行车
		bike := &Bike{
    
    
			Id:       uint32(i),
			Brand:    "凤凰牌",
			Location: "上海",
		}
		Store <- bike
		fmt.Printf("**%s生产者**:%d号%s牌自行车\n", (*bike).Location, (*bike).Id, (*bike).Brand)
		time.Sleep(time.Second * 2) //每隔两秒钟生产一辆自行车
	}
	//生产完之后,要关闭 Store,等到 Store 里的自行车被消费者买完以后,消费者会通过知道 Store 已经关闭了,而不再继续等待买 Store 里的自行车
	close(Store) //close 是非常有必要的!
}

func Consumer() {
    
     //自行车消费者
	for {
    
    
		bike, ok := <- Store
		if bike == nil && ok == false {
    
    
			break
		}
		fmt.Printf("==%s消费者==:%d号%s牌自行车\n", (*bike).Location, (*bike).Id, (*bike).Brand)
		time.Sleep(time.Second * 3) //消费者每隔 3 秒钟买一辆自行车
	}
	IsFinish <- true //告诉主线程 Store 里的自行车已经卖完了,生产者也不生产了,可以结束了
}

func main() {
    
    
	go Producer()
	go Consumer()
	<- IsFinish //在 IsFinish <- true 这句代码没执行前,主线程会阻塞在这里,这么做的目的就是防止上面两个协程还没执行完,主线程就提前退出了
}
  • Los resultados de la ejecución son los siguientes:
**上海生产者**:0号凤凰牌牌自行车
==上海消费者==:0号凤凰牌牌自行车
**上海生产者**:1号凤凰牌牌自行车
==上海消费者==:1号凤凰牌牌自行车
**上海生产者**:2号凤凰牌牌自行车
==上海消费者==:2号凤凰牌牌自行车
**上海生产者**:3号凤凰牌牌自行车
**上海生产者**:4号凤凰牌牌自行车
==上海消费者==:3号凤凰牌牌自行车
**上海生产者**:5号凤凰牌牌自行车
==上海消费者==:4号凤凰牌牌自行车
**上海生产者**:6号凤凰牌牌自行车
**上海生产者**:7号凤凰牌牌自行车
==上海消费者==:5号凤凰牌牌自行车
**上海生产者**:8号凤凰牌牌自行车
==上海消费者==:6号凤凰牌牌自行车
**上海生产者**:9号凤凰牌牌自行车
==上海消费者==:7号凤凰牌牌自行车
==上海消费者==:8号凤凰牌牌自行车
==上海消费者==:9号凤凰牌牌自行车

2.3, el uso combinado de canal y seleccionar

2.3.1 、 Caso de introducción
  • select se usa para que múltiples canales monitoreen y envíen y reciban mensajes. Cuando algún caso cumpla con las condiciones, se ejecutará. Si no hay un caso ejecutable, se ejecutará el predeterminado. Si no hay ningún predeterminado, el programa se bloqueará. .
func SelectSample() {
    
    
	c1 := make(chan string)
	go func(c chan string) {
    
    
		time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
		c <- "协程一来也!"
	}(c1)
	c2 := make(chan string)
	go func(c chan string) {
    
    
		time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
		c <- "协程二来也!"
	}(c2)

	for {
    
    
		select {
    
    
		case v, ok := <-c1:
			fmt.Println(v, ok)
		case v, ok := <- c2:
			fmt.Println(v, ok)
		default:
			fmt.Println("waiting")
		}
		time.Sleep(time.Second)
	}
}
  • Llame a la función SelectSample en la función principal, el resultado de la ejecución es el siguiente:
waiting
协程一来也! true
waiting
waiting
waiting
waiting
waiting
协程二来也! true
waiting
waiting
waiting
......
2.3.2, procesamiento de tiempo de espera
  • En muchos casos, usamos select para monitorear el canal, y luego damos el siguiente paso de acuerdo con el estado del canal, y a menudo usamos default para cooperar con la operación. El uso de default es ejecutar la instrucción que sigue a default cuando todos Los casos no coinciden, pero hay una situación en la que, aunque el estado del canal actualmente monitoreado no ha cambiado (el canal no ha escrito ni leído datos), el estado del canal puede cambiar después de un período de tiempo, por lo que en este momento, necesitamos para darle al programa algo de tiempo de espera, en lugar de ejecutar inmediatamente la instrucción que sigue por defecto. Hablando de esto, algunas personas pueden pensar que ya que está esperando por un período de tiempo, entonces puedo usar time.Sleep (time.Duration). Es cierto que sí, pero hay una pregunta, ¿cuánto tiempo quieres? ¿El programa para dormir? Bueno, no está seguro de cuánto tiempo dejar el programa en reposo (espere). Así que ahora presentaré otro método: time.After (time.Duration), la ejecución de esta función devuelve un canal, aquí también podría dejar timeout: = time.After (time.Duration), la función de esta función es el tiempo. Duración, ejecución y retorno a timeout. Es un canal. Como es un canal, entonces podemos ponerlo después de la condición de caso y "competir" con otros canales. No hay mucho que decir, el código:
func Timeout1() {
    
    
	c := make(chan string, 1)
	finish := make(chan bool)
	go func() {
    
    
		time.Sleep(time.Second * 3)
		c <- "message"
	}()

	go func() {
    
    
		for {
    
    
			select {
    
    
			case res := <-c:
				fmt.Println(res, time.Now())
				finish <- true
				break
			case val, ok := <-time.After(time.Second * 2):
				fmt.Println("timeout", val, ok)
			}
		}
	}()
	<- finish
}
  • Coloque el código anterior en main y ejecútelo, y el resultado de salida es el siguiente. A través del resultado de salida, se encuentra que el intervalo de tiempo de salida antes y después de las dos declaraciones es de 1 segundo
timeout 2021-03-26 20:55:44.47262 +0800 CST m=+2.007340901
message 2021-03-26 20:55:45.4725173 +0800 CST m=+3.007238201

3. Puntos a tener en cuenta

3.1, use el canal de salida iterativo para rango

  • Cuando use for-range para generar iterativamente los datos en el canal, debe cerrar explícitamente el canal en una posición determinada, de lo contrario se encontrará con una situación de punto muerto. Utilice un fragmento de código para demostrar:
func DeadLockSample() {
    
    
	c := make(chan int)
	go func(c chan int) {
    
    
		fmt.Println("in goroutine")
		c <- 100
		//close(c)
	}(c)
	for {
    
    
		val, ok := <-c
		fmt.Println(val, ok)
	}
}
  • El resultado de salida del código anterior ejecutado en la función principal es el siguiente:
in goroutine
100 true
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
goStudy/self/Channel.DeadLockSample()
	D:/program/Code/go/src/goStudy/self/Channel/test_channel.go:125 +0x7b
main.main()
	D:/program/Code/go/src/goStudy/self/main.go:56 +0x27

Process finished with exit code 2
  • Entonces, si se cancela el símbolo de comentario delante del cierre © anterior, ¿cuál será el resultado de la ejecución? Consulte el resultado de la ejecución a continuación:
in goroutine
100 true
0 false
0 false
0 false
0 false
......

3.2 Precauciones por tiempo. Después ()

  • La función time.After () volverá a cronometrar cada vez que se haga una selección. En términos más generales, time.After () reiniciará el cronometraje cada vez que se ejecute. Por ejemplo, el período de tiempo de espera que establecí es de 3 segundos., Hay una tarea que tarda 2 segundos en completarse una vez, y una vez que se completa la ejecución, escribe datos en un canal (trabajo), y luego esta tarea se ejecutará 5 veces en un bucle. Ahora, ahora tiempo. Después y ch son ambos esperando detrás de la condición de caso seleccionado. Suponemos que el momento en que el programa comienza a ejecutarse es 0 segundos, luego, en el segundo segundo, la tarea se ejecuta con éxito una vez, en el cuarto segundo, la tarea se ejecuta sin problemas nuevamente, hasta el décimo segundo, se ejecuta la tarea. Las tareas de horas extra no se activan y ejecutan una vez, sin explicación, solo vaya al código:
func Timeout4() {
    
    
	work := make(chan string)
	finish := make(chan bool)
	go func() {
    
    
		for i := 0; i < 5; i++ {
    
    
			time.Sleep(time.Second * 1)
			work <- "任务" + strconv.Itoa(i)
		}
	}()
	go func() {
    
    
		for {
    
    
			select {
    
    
			case val := <- work:
				fmt.Println(val)
				if strings.HasSuffix(val, "4") {
    
    
					finish <- true
				}
			case <-time.After(time.Second * 3):
				fmt.Println("超时了...")
			}
		}
	}()
	<- finish
}
  • Coloque el código anterior en main para su ejecución, y el resultado de la ejecución es el siguiente:
任务0
任务1
任务2
任务3
任务4

Supongo que te gusta

Origin blog.csdn.net/wxy_csdn_world/article/details/115218290
Recomendado
Clasificación