http://play.golang.org/p/vhaKi5uVmm
package main
import "fmt"
var battle = make(chan string)
func warrior(name string, done chan struct{}) {
select {
case opponent := <-battle:
fmt.Printf("%s beat %s\n", name, opponent)
case battle <- name:
// I lost :-(
}
done <- struct{}{}
}
func main() {
done := make(chan struct{})
langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
for _, l := range langs { go warrior(l, done) }
for _ = range langs { <-done }
}
[1ère question]
done <- struct{}{}
Comment et pourquoi avons-nous besoin de cette structure bizarre? Est-ce une structure vide ou une structure anonyme? Je l'ai recherché sur Google, mais je n'ai pas trouvé la bonne réponse ou la bonne documentation pour expliquer cela.
La source originale provient de la conférence d'Andrew Gerrand http://nf.wh3rd.net/10things/#1
Ici
make(chan struct{})
done est un canal de type struct {}
J'ai donc essayé avec
done <- struct{}
Mais ça ne fonctionne pas. Pourquoi ai-je besoin de supports supplémentaires pour cette ligne?
done <- struct{}{}
[2e question]
for _ = range langs { <-done }
Pourquoi ai-je besoin de cette ligne? Je sais que cette ligne est nécessaire car sans cette ligne, pas de sortie. Mais pourquoi et que fait cette ligne? Et qu'est-ce qui le rend nécessaire dans ce code? Je le sais <-done
est de recevoir les valeurs du canal fait et de rejeter les valeurs reçues. Mais pourquoi dois-je faire cela?
Les littéraux composites construisent des valeurs pour les structures, les tableaux, les tranches et les mappes et créent une nouvelle valeur chaque fois qu'ils sont évalués. Ils se composent du type de la valeur suivi d'une liste d'éléments composites liée à une accolade. Un élément peut être une expression unique ou une paire clé-valeur.
struct{}{}
est un littéral composite de type struct{}
, le type de la valeur suivi d'une liste d'éléments composites liée à une accolade.
for _ = range langs { <-done }
attend que tous les goroutines pour tous les langs
aient envoyé done
messages.
Notez qu'un aspect intéressant de l'utilisation de struct {} pour le type poussé vers un canal (par opposition à int ou bool), est que la taille d'un la structure vide est ... 0!
Voir le récent article " La structure vide " (mars 2014) par Dave Cheney .
Vous pouvez créer autant de struct{}
Que vous le souhaitez (struct{}{}
) Pour les pousser sur votre chaîne: votre mémoire ne sera pas affectée.
Mais vous pouvez l'utiliser pour la signalisation entre les routines go , comme illustré dans " Curious Channels ".
finish := make(chan struct{})
Comme le comportement de la
close(finish)
repose sur la signalisation de la fermeture du canal, et non sur la valeur envoyée ou reçue, déclarerfinish
commetype chan struct{}
Indique que le canal ne contient pas valeur; nous ne sommes intéressés que par sa propriété fermée.
Et vous conservez tous les autres avantages liés à une structure:
dans Go, vous pouvez utiliser une structure vide et stocker toutes vos données dans des variables globales. Il n'y aura qu'une seule instance du type, car toutes les structures vides sont interchangeables.
Voir par exemple le global var errServerKeyExchange
dans le fichier où le empty struct rsaKeyAgreement
est défini.
struct{}
est un type (en particulier, une structure sans membres). Si vous avez un type Foo
, vous pouvez créer une valeur de ce type dans une expression avec Foo{field values, ...}
. Mettre cela ensemble, struct{}{}
est une valeur de type struct{}
, ce à quoi la chaîne s'attend.
La fonction main
génère des goroutines warrior
, qui écriront sur le canal done
une fois qu'elles auront terminé. Le dernier bloc for
lit à partir de ce canal, garantissant que main
ne reviendra pas avant que tous les goroutines soient terminés. Ceci est important car le programme se fermera lorsque main
se terminera, qu'il y ait ou non d'autres goroutines en cours d'exécution.
Bonnes questions,
L'intérêt du canal struct dans ce scénario est simplement de signaler l'achèvement que quelque chose d'utile s'est produit. Le type de canal n'a pas vraiment d'importance, il aurait pu utiliser un int ou un bool pour obtenir le même effet. Ce qui est important, c'est que son code s'exécute de manière synchronisée où il fait la comptabilité nécessaire pour signaler et passer à des points clés.
J'accepte la syntaxe de struct{}{}
semble étrange au début, car dans cet exemple, il déclare une structure et la crée en ligne, d'où le deuxième ensemble de crochets.
Si vous aviez un objet préexistant comme:
type Book struct{
}
Vous pouvez le créer comme ceci: b := Book{}
, vous n'avez besoin que d'un seul ensemble de crochets car la structure Book a déjà été déclarée.
done
channel est utilisé pour recevoir les notifications de la méthode warrior
qui indique que le traitement est terminé. Ainsi, le canal peut être n'importe quoi, par exemple:
func warrior(name string, done chan bool) {
select {
case opponent := <-battle:
fmt.Printf("%s beat %s\n", name, opponent)
case battle <- name:
// I lost :-(
}
done <- true
}
func main() {
done := make(chan bool)
langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
for _, l := range langs { go warrior(l, done) }
for _ = range langs { <-done }
}
Nous déclarons done := make(chan bool)
comme un canal qui reçoit une valeur booléenne et envoyons true
à la fin de warrior
à la place. Cela marche! Vous pouvez également définir le canal done
sur n'importe quel autre type, cela n'aura pas d'importance.
1. Alors, quel est le bizarre done <- struct{}{}
?
C'est juste un autre type qui sera transmis au canal. Il s'agit d'une structure vide, si vous connaissez les éléments suivants:
type User struct {
Name string
Email string
}
struct{}
Ne fait aucune différence, sauf qu'il ne contient aucun champ, et struct{}{}
N'est qu'une instance de celui-ci. La meilleure caractéristique est qu'elle ne coûte pas d'espace mémoire!
2. Pour une utilisation en boucle
Nous créons 6 goroutines à exécuter en arrière-plan avec cette ligne:
for _, l := range langs { go warrior(l, done) }
Nous utilisons le for _ = range langs { <-done }
, Car le goroutine principal (où s'exécute la fonction principale) n'attend pas la fin des goroutins.
Si nous n'incluons pas la dernière ligne, il y a de fortes chances que nous ne voyions aucune sortie (car les goroutines principales se quittent avant que les goroutines enfants exécutent le code fmt.Printf
, Et lorsque le goroutine principal se ferme, tous les goroutines enfants se fermeront avec lui et pas de chance de courir de toute façon).
Nous attendons donc que tous les goroutines se terminent (il s'exécute jusqu'à la fin et envoyons un message au canal done
), puis quitte. done
channel ici est un canal bloqué, ce qui signifie que <-done
sera bloqué ici jusqu'à ce qu'un message soit reçu du canal.
Nous avons 6 goroutines en arrière-plan et utilisons for loop, nous attendons que tous les goroutines envoient un message qui signifie qu'il a fini de fonctionner (parce que done <-struct{}{}
Est à la fin de la fonction).