tag personalized nombre name first etiquetas campaña campaign concurrency go channels

concurrency - personalized - ¿Por qué usar un canal sin búfer en la misma rutina ofrece un punto muerto?



merge tags first name (3)

Estoy seguro de que hay una explicación simple para esta situación trivial, pero soy nuevo en el modelo de concurrencia de go .

cuando ejecuto este ejemplo

package main import "fmt" func main() { c := make(chan int) c <- 1 fmt.Println(<-c) }

Me sale este error:

fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.main() /home/tarrsalah/src/go/src/github.com/tarrsalah/tour.golang.org/65.go:8 +0x52 exit status 2

Por qué ?

Envolver c <- en un goroutine hace que el ejemplo se ejecute como esperábamos

package main import "fmt" func main() { c := make(chan int) go func(){ c <- 1 }() fmt.Println(<-c) }

Nuevamente, ¿por qué?

Por favor, necesito una explicación profunda, no solo cómo eliminar el interbloqueo y corregir el código.


De la documentación :

Si el canal está sin búfer, el emisor bloquea hasta que el receptor haya recibido el valor. Si el canal tiene un buffer, el emisor bloquea solo hasta que el valor haya sido copiado al buffer; si el buffer está lleno, esto significa esperar hasta que algún receptor haya recuperado un valor.

Dicho de otra manera:

  • cuando un canal está lleno, el emisor espera que otro goroutine haga espacio al recibir
  • puede ver un canal sin búfer como uno siempre lleno: debe haber otra rutina para tomar lo que envía el remitente.

Esta línea

c <- 1

bloques porque el canal no está búfer. Como no hay otra rutina para recibir el valor, la situación no puede resolverse, este es un punto muerto.

Puede hacer que no bloquee cambiando la creación del canal a

c := make(chan int, 1)

para que haya espacio para un elemento en el canal antes de que se bloquee.

Pero eso no es de lo que se trata la concurrencia. Normalmente, no usarías un canal sin otros goroutines para manejar lo que colocas dentro. Podrías definir una rutina de recepción como esta:

func main() { c := make(chan int) go func() { fmt.Println("received:", <-c) }() c <- 1 }

Demonstration


En el canal sin búfer, la escritura en el canal no ocurrirá hasta que deba haber algún receptor que esté esperando recibir los datos, lo que significa que en el ejemplo siguiente

func main(){ ch := make(chan int) ch <- 10 /* Main routine is Blocked, because there is no routine to receive the value */ <- ch }

Ahora, en caso de que tengamos otra rutina, se aplica el mismo principio

func main(){ ch :=make(chan int) go task(ch) ch <-10 } func task(ch chan int){ <- ch }

Esto funcionará porque la rutina de tareas está esperando que los datos se consuman antes de que las escrituras pasen al canal sin búfer.

Para hacerlo más claro, intercambiemos el orden de los enunciados segundo y tercero en la función principal.

func main(){ ch := make(chan int) ch <- 10 /*Blocked: No routine is waiting for the data to be consumed from the channel */ go task(ch) }

Esto conducirá a un punto muerto

En resumen, las escrituras en el canal sin búferes ocurren solo cuando hay alguna rutina esperando a leer del canal, de lo contrario, la operación de escritura queda bloqueada para siempre y conduce a un punto muerto.

NOTA : El mismo concepto se aplica al canal almacenado, pero el Remitente no se bloquea hasta que el buffer esté lleno, lo que significa que el receptor no necesariamente se sincronizará con cada operación de escritura.

Entonces, si tenemos un buffer de tamaño 1, entonces su código mencionado anteriormente funcionará

func main(){ ch := make(chan int, 1) /*channel of size 1 */ ch <-10 /* Not blocked: can put the value in channel buffer */ <- ch }

Pero si escribimos más valores en el ejemplo anterior, entonces ocurrirá un punto muerto

func main(){ ch := make(chan int, 1) /*channel Buffer size 1 */ ch <- 10 ch <- 20 /*Blocked: Because Buffer size is already full and no one is waiting to recieve the Data from channel */ <- ch <- ch }


En esta respuesta, trataré de explicar el mensaje de error a través del cual podemos echar un vistazo un poco sobre cómo funcionan los canales y los goroutines.

El primer ejemplo es:

package main import "fmt" func main() { c := make(chan int) c <- 1 fmt.Println(<-c) }

El mensaje de error es:

fatal error: all goroutines are asleep - deadlock!

En el código, NO hay goroutines en absoluto (por cierto, este error está en tiempo de ejecución, no en tiempo de compilación). Cuando go ejecuta esta línea c <- 1 , quiere asegurarse de que el mensaje en el canal se reciba en alguna parte (es decir, <-c ). Go NO sabe si el canal se recibirá o no en este punto. Así que ir esperará a que los goroutines en ejecución terminen hasta que suceda cualquiera de los siguientes:

  1. todos los goroutines están terminados (dormidos)
  2. uno de los goroutine intenta recibir el canal

En el caso n. ° 1, aparece el error de salida con el mensaje anterior, ya que ahora se CONOCE que no hay forma de que un administrador reciba el canal y lo necesita.

En el caso n. ° 2, el programa continuará, ya que ahora se CONOCE que se recibió este canal. Esto explica el caso exitoso en el ejemplo de OP.