2016-03-21 3 views
1

Ich habe diesen Code. Ich erwarte, dass die Ausgabe:Wie man eine Methode als Goroutiner-Funktion verwendet

hello : 1 
world : 2 

aber es gibt:

world : 2 
world : 2 

Gibt es etwas falsch mit meinem Code?

package main 

import (
    "fmt" 
    "time" 
) 

type Task struct { 
    name string 
    data int32 
} 

func (this *Task) PrintData() { 
    fmt.Println(this.name, ":", this.data) 
} 

func main() { 
    tasks := []Task{{"hello", 1}, {"world", 2}} 
    for _, task := range tasks { 
     go task.PrintData() 
    } 
    time.Sleep(time.Second * 5000) 
} 

Antwort

7

PrintData Da ein Zeiger-Empfänger ist und task ein Wert ist, nimmt der Compiler automatisch die Adresse des task, wenn das Verfahren Anruf. Der resultierende Anruf ist der gleiche wie (&task).PrintData().

Die Variable task wird bei jeder Iteration durch die Schleife auf einen anderen Wert gesetzt. Die erste Goroutine wird erst ausgeführt, wenn task auf den zweiten Wert eingestellt ist. Führen Sie this example aus, um zu sehen, dass dieselbe Adresse bei jeder Iteration an PrintData übergeben wird.

Es gibt einige Möglichkeiten, dies zu beheben. Die erste ist *Task in der Schicht zu verwenden:

tasks := []*Task{{"hello", 1}, {"world", 2}} 
for _, task := range tasks { 
    go task.PrintData() 
} 

playground example

Die zweite ist eine neue Variable in der Schleife zu erzeugen:

tasks := []Task{{"hello", 1}, {"world", 2}} 
for _, task := range tasks { 
    task := task 
    go task.PrintData() 
} 

playground example

Eine dritte ist, Nimm die Adresse des Scheibenelements (unter Verwendung der automatisch eingefügten Adressenoperation):

tasks := []Task{{"hello", 1}, {"world", 2}} 
for i := range tasks { 
    go tasks[i].PrintData() 
} 

playground example

Eine weitere Option ist Druckdaten auf einen Wert Empfänger zu ändern, um den Methodenaufruf automatisch das Ermitteln die Adresse task zu verhindern:

func (this Task) PrintData() { 
    fmt.Println(this.name, ":", this.data) 
} 

playground example

Dieses Problem ist ähnlich wie die issue discussed in the closures and goroutines FAQ. Der Unterschied zwischen den Problemen besteht in dem Mechanismus, der zum Übergeben eines Zeigers an die Goroutinenfunktion verwendet wird. Der Code in der Frage verwendet das Empfängerargument der Methode. Der Code in den FAQ verwendet eine closure.

+0

Danke, die dritte ist besser, weil ich das Mitglied der Struktur ändern möchte. – Devin

+0

@Devin, dritte Option ist die einzige Möglichkeit, wenn Sie das Segment ändern möchten. Bitte beachten Sie die aktualisierte dritte Option. Es ist weniger ausführlich als das Original. –

+1

@Devin Erste Option würde auch normalerweise funktionieren (solange Ihre Absicht darin besteht, die 'Task' zu ändern und den Zeiger im Slice nicht durch einen neuen Zeiger zu ersetzen), aber die dritte Option ist noch besser. – hobbs

1

Go Frequently Asked Questions (FAQ)

What happens with closures running as goroutines?

Einige Verwirrung entstehen, wenn Verschlüsse mit gemeinsamen Zugriff verwenden. Betrachten Sie das folgende Programm:

func main() { 
    done := make(chan bool) 

    values := []string{"a", "b", "c"} 
    for _, v := range values { 
     go func() { 
      fmt.Println(v) 
      done <- true 
     }() 
    } 

    // wait for all goroutines to complete before exiting 
    for _ = range values { 
     <-done 
    } 
} 

man erwarten könnte fälschlicherweise ein, um zu sehen, b, c als Ausgang. Was Sie wahrscheinlich sehen werden, ist c, c, c. Dies liegt daran, dass für jede Iteration der Schleife die gleiche Instanz der Variablen v verwendet wird, so dass jeder Abschluss diese einzelne Variable gemeinsam nutzt. Wenn der Abschluss ausgeführt wird, wird der Wert von v zu dem Zeitpunkt ausgegeben, zu dem fmt.Println ausgeführt wird, aber v wurde möglicherweise seit dem Start der Goroutine geändert. Um zu helfen, diese und andere Probleme zu erkennen, bevor sie passieren, führen Sie go vet.

Um den aktuellen Wert von v an jedes Schließelement beim Start zu binden, muss eine die innere Schleife ändern, um bei jeder Iteration eine neue Variable zu erstellen. Eine Möglichkeit ist die Variable als ein Argument an den Verschluss passieren:

for _, v := range values { 
    go func(u string) { 
     fmt.Println(u) 
     done <- true 
    }(v) 
} 

In diesem Beispiel ist der Wert von v wird als Argument an die anonyme Funktion übergeben. Dieser Wert ist dann innerhalb der Funktion als Variable u zugänglich.

Noch einfacher ist nur eine neue Variable zu erstellen, eine Erklärung Stil verwenden, das mag seltsam erscheinen, aber funktioniert in Go fein:

for _, v := range values { 
    v := v // create a new 'v'. 
    go func() { 
     fmt.Println(v) 
     done <- true 
    }() 
} 

einfach eine neue Variable für die Schließung erstellen mit ein Deklarations-Stil, der seltsam erscheinen mag, aber in Go gut funktioniert. Fügen Sie task := task hinzu. Zum Beispiel

package main 

import (
    "fmt" 
    "time" 
) 

type Task struct { 
    name string 
    data int32 
} 

func (this *Task) PrintData() { 
    fmt.Println(this.name, ":", this.data) 
} 

func main() { 
    tasks := []Task{{"hello", 1}, {"world", 2}} 
    for _, task := range tasks { 
     task := task 
     go task.PrintData() 
    } 
    time.Sleep(time.Second * 5000) 
} 

Output:

hello : 1 
world : 2 
+0

@MuffinTop: Sie behaupten, ist eindeutig falsch. Lesen Sie [Die Go-Programmiersprache-Spezifikation] (https://golang.org/ref/spec). – peterSO

+0

Danke, und danke für meine Frage zu bearbeiten, verstehe ich jetzt – Devin

Verwandte Themen