Por qué usar un canal sin búfer en el mismo goroutine da un punto muerto


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

Cuando corro este ejemplo

package main

import "fmt"

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

Recibo 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)
}

De nuevo, ¿por qué ?

Por favor, necesito una explicación profunda , no solo cómo eliminar el punto muerto y arreglar el código.

Author: Cœur, 2013-09-06

3 answers

De la documentación :

Si el canal está sin búfer, el remitente se bloquea hasta que el receptor ha recibido el valor. Si el canal tiene un búfer, el remitente bloquea solo hasta que el valor se ha copiado al búfer; si el búfer está lleno, esto significa esperando hasta que algún receptor haya recuperado un valor.

Dijo lo contrario:

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

Esta línea

c <- 1

Se bloquea porque el canal no tiene búfer. Como no hay otro goroutine para recibir el valor, la situación no se puede resolver, esto es un punto muerto.

Puede hacer que no se 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 él bloque.

Pero de eso no se trata la concurrencia. Normalmente, no usarías un canal sin otros goroutines para manejar lo que pones dentro. Se podría definir una goroutine receptora como esta:

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

Demostración

 57
Author: Denys Séguret,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-26 01:55:13

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

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 go, 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 la tarea está esperando que los datos se consuman antes de que las escrituras ocurran en el canal sin búfer.

Para que sea más claro, permite intercambiar el orden de la segunda y tercera instrucciones en 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 conduce a un punto muerto

Así que en resumen, las escrituras en un canal sin búfer solo ocurren cuando hay alguna rutina esperando para leer desde el canal, de lo contrario la operación de escritura se bloquea para siempre y conduce a un punto muerto.

NOTA: El mismo concepto se aplica al canal en búfer, pero el remitente no se bloquea hasta que el búfer está lleno, lo que significa que el receptor no necesariamente debe sincronizarse con cada operación de escritura.

Así que si hemos amortiguado canal de tamaño 1, entonces el 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
}
 10
Author: bharatj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-17 09:24:38

En esta respuesta, intentaré explicar el mensaje de error a través del cual podemos echar un vistazo un poco a cómo funciona go en términos de canales y 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 será recibido en algún lugar (es decir, <-c). Ir no sabe si el canal será recibido o no en este punto. Así que go esperará a que terminen las goroutinas en ejecución hasta que ocurra una de las siguientes situaciones:

  1. todas las goroutinas están terminadas (dormidas){[14]]}
  2. uno de los goroutine intenta recibir el canal

En el caso #1, go se equivocará con el mensaje anterior, ya que ahora go SABE que no hay forma de que un goroutine reciba el canal y lo necesite.

En el caso # 2, el programa continuará, ya que ahora go SABE que este canal es recibido. Esto explica el caso exitoso en el ejemplo de OP.

 1
Author: Chun Yang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-26 14:35:29