Golang: struttura anonima e struttura vuota

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

[1a domanda]

  done <- struct{}{} 

Come e perché abbiamo bisogno di questa struttura dall’aspetto strano? E ‘una struttura vuota o una struttura anonima? L’ho cercato su Google ma non sono riuscito a trovare la risposta o la documentazione giusta per spiegarlo.

La fonte originale è tratta dal discorso di Andrew Gerrand http://nf.wh3rd.net/10things/#10

Qui

  make(chan struct{}) 

fatto è un canale di tipo struct {}

Così ho provato con

  done <- struct{} 

Ma non sta funzionando. Perché ho bisogno di parentesi aggiuntive per questa linea?

  done <- struct{}{} 

[2a domanda]

  for _ = range langs { <-done } 

Perché ho bisogno di questa linea? So che questa linea è necessaria perché senza questa linea, nessuna uscita. Ma perché e cosa fa questa linea? E cosa rende necessario in questo codice? So che <-done sta per ricevere i valori dal canale fatto e scartare i valori ricevuti. Ma perché ho bisogno di fare questo?

Grazie!

Letterali compositi

I valori letterali compositi costruiscono valori per strutture, matrici, sezioni e mappe e creano un nuovo valore ogni volta che vengono valutati. Consistono nel tipo del valore seguito da un elenco di elementi compositi rilegato. Un elemento può essere una singola espressione o una coppia chiave-valore.

struct{}{} è un letterale composito di tipo struct{} , il tipo del valore seguito da un elenco vincolato a coppie di elementi compositi.

for _ = range langs { <-done } sta aspettando che tutte le goroutine di tutti i langs abbiano inviato messaggi.

Si noti che un aspetto interessante dell’uso di struct {} per il tipo spinto su un canale (al contrario di int o bool), è che la dimensione di una struttura vuota è … 0!

Vedi il recente articolo ” The empty struct ” (marzo 2014) di Dave Cheney .

Puoi creare tutte le struct{} che vuoi ( struct{}{} ) per inviarle al tuo canale: la tua memoria non sarà influenzata.
Ma puoi usarlo per la segnalazione tra le routine di go, come illustrato in ” Canali curiosi “.

E conservi tutti gli altri vantaggi legati a una struttura:

  • puoi definire i metodi su di esso (quel tipo può essere un ricevitore del metodo)
  • puoi implementare un’interfaccia (con i metodi che hai appena definito sulla tua struttura vuota)
  • come un singleton

in Go puoi usare una struttura vuota e memorizzare tutti i tuoi dati in variabili globali. Ci sarà solo un’istanza del tipo, poiché tutte le strutture vuote sono intercambiabili.

Si veda ad esempio la errServerKeyExchange globale var errServerKeyExchange nel file in cui è definita la struttura vuota rsaKeyAgreement .

  1. struct{} è un tipo (in particolare, una struttura senza membri). Se hai un tipo Foo , puoi creare un valore di quel tipo in un’espressione con Foo{field values, ...} . Mettendo questo insieme, struct{}{} è un valore del tipo struct{} , che è ciò che il canale si aspetta.

  2. La funzione main genera le goroutine dei warrior , che scriveranno sul canale done quando avranno finito. L’ultimo for blocco legge da questo canale, assicurando che il main non ritorni fino a quando tutte le goroutine non avranno finito. Questo è importante perché il programma uscirà quando il main termina, indipendentemente dal fatto che ci siano altre goroutine in esecuzione.

Buone domande,

L’intero punto del canale struct in questo scenario è semplicemente quello di segnalare il completamento che è successo qualcosa di utile. Il tipo di canale non ha molta importanza, potrebbe aver usato un int o un bool per ottenere lo stesso effetto. Ciò che è importante è che il suo codice sia eseguito in modo sincronizzato, dove sta facendo la contabilità necessaria per segnalare e andare avanti nei punti chiave.

Sono d’accordo che la syntax di struct{}{} sembra strana all’inizio, perché in questo esempio sta dichiarando una struct e creando in-line da qui il secondo set di parentesi.

Se avessi un object preesistente come:

 type Book struct{ } 

Puoi crearlo in questo modo: b := Book{} , hai solo bisogno di un set di parentesi perché la struttura del Libro è già stata dichiarata.

canale done viene utilizzato per ricevere notifiche dal metodo warrior che indica che il lavoratore ha terminato l’elaborazione. Quindi il canale può essere qualsiasi cosa, ad esempio:

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

Dichiariamo done := make(chan bool) come canale che riceve il valore bool e invia invece true alla fine del warrior . Funziona! Puoi anche definire il canale done su qualsiasi altro tipo, non importa.

1. Allora, cos'è lo strano done <- struct{}{} ?

È solo un altro tipo che verrà passato al canale. Questa è una struttura vuota, se si ha familiarità con quanto segue:

 type User struct { Name string Email string } 

struct{} non fa differenza, tranne che non contiene campi, e struct{}{} è solo un'istanza fuori da esso. La migliore caratteristica è che non costa spazio in memoria!

2. per l'utilizzo del loop

Creiamo 6 goroutine per correre sullo sfondo con questa linea:

  for _, l := range langs { go warrior(l, done) } 

Usiamo for _ = range langs { <-done } , perché la goroutine principale (dove viene eseguita la funzione principale) non aspetta la fine delle goroutine.

Se non includiamo l'ultimo per la linea, è probabile che non vediamo output (perché la goroutine principale si interrompe prima che qualsiasi goroutina figlio esegua il codice fmt.Printf e quando la goroutine principale si chiude, tutte le goroutine di bambini si chiuderanno con esso e non avranno possibilità di correre comunque).

Quindi aspettiamo che tutte le goroutine finiscano (corre fino alla fine e invia un messaggio al canale done ), quindi esce. done canale done qui è un canale bloccato, il che significa che <-done bloccherà qui finché non verrà ricevuto un messaggio dal canale.

Abbiamo 6 goroutine in background, e usiamo il ciclo, aspettiamo che tutte le goroutine inviino un messaggio, il che significa che ha finito di funzionare (perché il done <-struct{}{} sia alla fine della funzione).