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:
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):
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 chanboolfuncinit(){
//初始化函数,先于 main 函数执行
Store =make(chan*Bike,5)
IsFinish =make(chanbool)}funcProducer(){
//自行车生产者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 是非常有必要的!}funcConsumer(){
//自行车消费者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 里的自行车已经卖完了,生产者也不生产了,可以结束了}funcmain(){
goProducer()goConsumer()<- IsFinish //在 IsFinish <- true 这句代码没执行前,主线程会阻塞在这里,这么做的目的就是防止上面两个协程还没执行完,主线程就提前退出了}
Los resultados de la ejecución son los siguientes:
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á. .
funcSelectSample(){
c1 :=make(chanstring)gofunc(c chanstring){
time.Sleep(time.Duration(rand.Intn(10))* time.Second)
c <-"协程一来也!"}(c1)
c2 :=make(chanstring)gofunc(c chanstring){
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:
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:
funcTimeout1(){
c :=make(chanstring,1)
finish :=make(chanbool)gofunc(){
time.Sleep(time.Second *3)
c <-"message"}()gofunc(){
for{
select{
case res :=<-c:
fmt.Println(res, time.Now())
finish <-truebreakcase 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
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:
funcDeadLockSample(){
c :=make(chanint)gofunc(c chanint){
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
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:
funcTimeout4(){
work :=make(chanstring)
finish :=make(chanbool)gofunc(){
for i :=0; i <5; i++{
time.Sleep(time.Second *1)
work <-"任务"+ strconv.Itoa(i)}}()gofunc(){
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: