2016-06-27 25 views
5
type driver struct { 
    variables map[string]string 
} 

var Drivers []driver 

func main() { 

    driver := driver{ 
     variables: make(map[string]string), 
    } 
    Drivers = append(Drivers, driver) 

    driver.variables = make(map[string]string) // Commenting this line makes it work, too 

    done := make(chan bool) 
    go driver.populate(done) 

    <-done 

    fmt.Print(Drivers[0].variables) 
} 

func (this *driver) populate(done chan bool) { 
    time.Sleep(500 * time.Millisecond) 
    this.variables["a"] = "b" 
    done <- true 
} 

mi aspettavo:Perché questa mappa è vuota quando la popolo in una Goroutine?

map[a:b] 

Risultato effettivo:

map[] 

Playground

risposta

5

Il problema è semplice: si dispone di una fetta di driver s:

var Drivers []driver 

noti che Drivers è una porzione di qualche tipo di struct, non una porzione di puntatori!

Quando si aggiunge qualcosa (o si assegna un valore ad uno dei suoi elementi):

Drivers = append(Drivers, driver) 

che fa una copia del valore aggiunto (o assegnati)! Quindi, quando si esegue questa operazione in seguito:

driver.variables = make(map[string]string) 

Sarà impostato un nuovo valore di mappa per driver.variables, ma che è distinto dal valore memorizzato in Drivers (più precisamente a Drivers[0]).

Successivamente si popola driver.variables, ma si stampa Drivers[0].variables. Sono 2 diversi valori di struttura, con 2 valori di mappa diversi. Le goroutine non hanno un ruolo qui (sono correttamente sincronizzate quindi non dovrebbero comunque).

Vuoi stampare driver.variables:

fmt.Print(driver.variables) 

vedreste (provate sul Go Playground):

map[a:b] 

Se si commenta questa riga:

driver.variables = make(map[string]string) // Commenting this line makes it work, too 

Funzionerebbe, ma solo perché pur avendo 2 valori struct, hanno lo stesso valore di mappa (stessa intestazione della mappa che punta alla stessa struttura dati della mappa).

Si può anche farlo funzionare se si chiama driver.populate() sul valore struct Drivers[0] (e attaccare alla stampa Drivers[0].variables):

go Drivers[0].populate(done) 

// ... 

fmt.Print(Drivers[0].variables) 

provare questo uno sul Go Playground.

E si può anche farlo funzionare se Drivers è una fetta di puntatori:

var Drivers []*driver 

// ... 

driver := &driver{ 
    variables: make(map[string]string), 
} 

Perché driver e Driver[0] sarà lo stesso puntatore (si avrà solo un valore struct e un valore mappa come l'iniziale la mappa non è più accessibile). Prova questo su Go Playground.

+4

Questa è la risposta corretta: le goroutine in questo caso sono irrilevanti: https://play.golang.org/p/Eh3gPbFWPL mostra il problema con solo la goroutine principale; capendo che le strutture sono _copiate_, ma i puntatori (e i tipi di riferimento, come la mappa) sono condivisi è ciò che sta causando il problema qui. – val

1

Il motivo per cui utilizzando la versione goroutine non si ottiene una mappa inizializzata è che quando la funzione principale restituisce, il programma termina: non attende che altre (non principali) goroutine completino. Si prega di essere consapevoli che la funzione principale è di per sé una goroutine.

Quindi, anche se si inizializza il mappa utilizzando:

driver.variables = make(map[string]string) 

non significa che in realtà popolato con i valori, si sono inizializzare solo una struttura dati hash mappa e restituisce un valore di carta che fa riferimento a esso.

I tipi di mappa sono tipi di riferimento, come puntatori o sezioni, e quindi il valore di m sopra è nullo; non punta a una mappa inizializzata. Una mappa n. si comporta come una mappa vuota durante la lettura, ma i tentativi di scrittura su una mappa n. causano un errore di runtime; non farlo Per inizializzare una mappa , utilizzare la funzione di creazione incorporata.

Nel caso in cui si rimuove la parola chiave go primo momento sarà inizializzare la mappa driver.variables. Ma poiché è in esecuzione nella stessa thread (il thread principale) e si imposta una temporizzazione sulla funzione populate per prima cosa inizializzerà la mappa, quindi la popolerà.

+0

Non capisco. La goroutine populate() non viene eseguita mentre la goroutine principale esegue time.Sleep (1000ms)? – AndreKR

+0

Dopo il suggerimento di Petr ho aggiornato la mia domanda. Penso che la tua spiegazione non si applica più, ma il comportamento è sempre lo stesso. – AndreKR

+0

Sì, è in esecuzione, ma 'driver.variables = make (map [string] string)' verrà reinizializzato se si chiama la funzione 'populate' su un'altra goroutine. –

0

farei meglio canali usare al posto di sleep s:

package main 

import (
    "fmt" 
    "time" 
) 

type driver struct { 
    variables map[string]string 
} 

var Drivers []driver 

func main() { 
    driver := driver{ 
     variables: make(map[string]string), 
    } 
    Drivers = append(Drivers, driver) 

    done := make(chan bool) 
    go driver.populate(done) 
    <-done // wait for goroutine to complete 

    fmt.Print(Drivers[0].variables) 
} 

func (this *driver) populate(done chan bool) { 
    time.Sleep(500 * time.Millisecond) 
    this.variables["a"] = "b" 
    done <- true 
} 

Playground

+0

Ottima idea. La tua versione mostra ancora lo stesso problema ed elimina tutti i dubbi sulle condizioni di gara. Ho aggiornato la mia domanda. – AndreKR

+0

@AndreKR sì, infatti, ho appena notato che non è correlato alle goroutine. Si utilizza una porzione di struct, non una porzione di puntatori. Funziona con una serie di puntatori: https://play.golang.org/p/R8Vkwh_J9H –

Problemi correlati