web-dev-qa-db-fra.com

Pourquoi utiliser un canal sans tampon dans le même goroutine donne un blocage

Je suis sûr qu'il existe une explication simple à cette situation triviale, mais je suis nouveau dans le modèle de concurrence go.

quand je lance cet exemple

package main

import "fmt"

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

Je reçois cette erreur:

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

Pourquoi ?


Emballage c <- dans un goroutine fait fonctionner l'exemple comme prévu

package main

import "fmt"

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

Encore une fois, pourquoi?

S'il vous plaît, j'ai besoin d'une explication approfondie, pas seulement comment éliminer l'impasse et corriger le code.

42
tarrsalah

De la documentation :

Si le canal n'est pas mis en mémoire tampon, l'émetteur bloque jusqu'à ce que le récepteur ait reçu la valeur. Si le canal a un tampon, l'expéditeur ne bloque que jusqu'à ce que la valeur ait été copiée dans le tampon; si le tampon est plein, cela signifie attendre qu'un récepteur ait récupéré une valeur.

Dit autrement:

  • lorsqu'un canal est plein, l'expéditeur attend qu'un autre goroutine fasse de la place en recevant
  • vous pouvez voir un canal sans tampon comme un canal toujours plein: il doit y avoir un autre goroutine pour prendre ce que l'expéditeur envoie.

Cette ligne

c <- 1

bloque parce que le canal est sans tampon. Comme il n'y a pas d'autre goroutine pour recevoir la valeur, la situation ne peut pas se résoudre, c'est une impasse.

Vous pouvez le faire ne pas bloquer en modifiant la création de la chaîne en

c := make(chan int, 1) 

afin qu'il y ait de la place pour un élément dans le canal avant qu'il ne se bloque.

Mais ce n'est pas de cela qu'il s'agit. Normalement, vous n'utiliseriez pas un canal sans d'autres goroutines pour gérer ce que vous mettez à l'intérieur. Vous pouvez définir un goroutine récepteur comme celui-ci:

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

Démonstration

70
Denys Séguret

Dans un canal sans tampon, l'écriture sur le canal n'aura pas lieu tant qu'il ne doit pas y avoir un récepteur qui attend de recevoir les données, ce qui signifie dans l'exemple ci-dessous

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

Maintenant, dans le cas où nous avons une autre routine de go, le même principe s'applique

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

Cela fonctionnera parce que la routine de tâche attend que les données soient consommées avant que les écritures ne se produisent sur le canal sans tampon.

Pour le rendre plus clair, permutons l'ordre des deuxième et troisième instructions dans la fonction principale.

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

Cela conduira à Deadlock

Donc, en bref, les écritures sur le canal sans tampon ne se produisent que lorsqu'il y a une routine en attente de lecture sur le canal, sinon l'opération d'écriture est bloquée pour toujours et conduit à un blocage.

NOTE : Le même concept s'applique au canal en mémoire tampon, mais l'expéditeur n'est pas bloqué tant que le tampon n'est pas plein, ce qui signifie que le récepteur ne doit pas nécessairement être synchronisé avec chaque écriture opération.

Donc, si nous avons un canal tampon de taille 1, votre code mentionné ci-dessus fonctionnera

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

Mais si nous écrivons plus de valeurs dans l'exemple ci-dessus, un blocage se produira

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
bharatj

Dans cette réponse, je vais essayer d'expliquer le message d'erreur à travers lequel nous pouvons jeter un coup d'œil sur le fonctionnement de go en termes de canaux et de goroutines

Le premier exemple est:

package main

import "fmt"

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

Le message d'erreur est:

fatal error: all goroutines are asleep - deadlock!

Dans le code, il n'y a AUCUN goroutines du tout (BTW cette erreur est à l'exécution, pas à la compilation). Lorsque go exécute cette ligne c <- 1, il veut s'assurer que le message dans le canal sera reçu quelque part (c'est-à-dire <-c). Go ne sait PAS si la chaîne sera reçue ou non à ce stade. Alors allez attendre que les goroutines en cours d'exécution se terminent jusqu'à ce que l'un des événements suivants se produise:

  1. tous les goroutines sont finis (endormis)
  2. l'un des goroutins essaie de recevoir la chaîne

Dans le cas n ° 1, go affichera une erreur avec le message ci-dessus, puisque maintenant go sait qu'il n'y a aucun moyen qu'un goroutine reçoive le canal et qu'il en ait besoin.

Dans le cas # 2, le programme continuera, puisque maintenant allez SAIT que cette chaîne est reçue. Cela explique le cas réussi dans l'exemple de OP.

1
Chun Yang
  • La mise en mémoire tampon supprime la synchronisation.
  • La mise en mémoire tampon les rend plus semblables aux boîtes aux lettres d'Erlang.
  • Les canaux tamponnés peuvent être importants pour certains problèmes, mais ils sont plus subtils à raisonner
  • Par défaut, les canaux ne sont pas tamponnés, ce qui signifie qu'ils n'accepteront que les envois
    (chan <-) s'il y a un récepteur correspondant (<- chan) prêt à recevoir la valeur envoyée.
  • Les canaux en mémoire tampon acceptent un nombre limité de valeurs sans récepteur correspondant pour ces valeurs.

messages: = make (chaîne chan, 2) // - canal de chaînes tamponnant jusqu'à 2 valeurs.

Les envois et les réceptions de base sur les canaux bloquent. Cependant, nous pouvons utiliser select avec une clause default pour implémenter non bloquant envoie, reçoit et même non-bloquant multi-voies selects .

0
Awesome Infinity