2017-12-29 18 views
1

Lesen Was ich[Go]: Eine Datei line-by-line mit Concurrency

In GetLine tun möchte, versuche ich eine Datei Zeile-für-Zeile zu analysieren bufio.Scanner und ein naiver Versuch unter Verwendung von Nebenläufigkeit. Nach dem Abrufen des Textes in jeder Zeile, ich sende es über einen Kanal von string an den Aufrufer (main Funktion). Zusammen mit dem Wert sende ich auch Fehler und Abschlussflag (über done Kanal). Daher sollte es möglich sein, eine neue Zeile zu holen, die in einer separaten Routine verarbeitet wird, während die aktuelle Zeile verarbeitet wird.

Was ich eigentlich getan haben

var READCOMPLETE = errors.New("Completed Reading") 

func main() { 

    filename := flag.String("filename", "", "The file to parse") 
    flag.Parse() 

    if *filename == "" { 
     log.Fatal("Provide a file to parse") 
    } 

    fmt.Println("Getting file") 

    names := make(chan string) 
    readerr := make(chan error) 
    done := make(chan bool) 

    go GetLine(*filename, names, readerr, done) 

    for { 
     select { 
     case name := <-names: 
      // Process each line 
      fmt.Println(name) 

     case err := <-readerr: 
      log.Fatal(err) 

     case <-done: 
      // close(names) 
      // close(readerr) 
      break 
     } 
    } 

    fmt.Println("Processing Complete") 
} 

func GetLine(filename string, names chan string, readerr chan error, done chan bool) { 
    file, err := os.Open(filename) 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer file.Close() 

    scanner := bufio.NewScanner(file) 
    for scanner.Scan() { 
     names <- scanner.Text() 
     //fmt.Println(scanner.Text()) 
    } 

    if err := scanner.Err(); err != nil { 
     readerr <- err 
    } 

    done <- true 
} 

Was bekomme ich auf Laufen

Laufzeitfehler: fatal error: all goroutines are asleep - deadlock!

Was habe ich zu Fix Versuchte?

Nach this Antwort über die Fehlermeldung zu lesen, habe ich versucht, names und readerr in der letzten Klausel der select Anweisung, um die Kanäle zu schließen, wie in den Kommentaren gezeigt. Das Programm stürzt jedoch weiterhin mit einer Protokollnachricht ab. Ich kann es nicht weiter beheben und würde mich über jede Hilfe freuen.
Ressourcen zum Lernen sind willkommen.

P.S: Ich bin relativ neu bei GoLang und lerne immer noch, wie man mit dem CSP-Modell der Parallelität in Go arbeitet. Tatsächlich ist dies mein erster Versuch, ein synchrones konkurrierendes Programm zu schreiben.

+0

Die Ausgabe des Deadlock-Fehlers sollte Ihnen genau sagen, in welcher Zeile jede Goroutine steckt, was Ihnen helfen sollte, herauszufinden, warum das passiert. – Adrian

+0

@Adrian Danke für die Antwort. Jetzt habe ich herausgefunden, dass das Problem mit der vollständigen Ausführung der'GetLine'Goroutine zusammenhängt, während die 'select' Anweisung immer noch erwartet, einen Wert zu erhalten. Daher wird dieser Fehler ausgelöst. Habe ich recht? –

Antwort

2

Die break-Anweisung in einer Auswahl bricht aus der Auswahl. Die Anwendung muss nach dem Beenden der for-Schleife ausbrechen. Verwenden Sie ein Etikett, um die for-Schleife zu verlassen:

loop: 
    for { 
     select { 
     case name := <-names: 
      // Process each line 
      fmt.Println(name) 

     case err := <-readerr: 
      log.Fatal(err) 

     case <-done: 
      // close(names) 
      // close(readerr) 
      break loop 
     } 
    } 

Der Code kann vereinfacht werden, indem der Kanal done eliminiert wird.

func main() { 

    filename := flag.String("filename", "", "The file to parse") 
    flag.Parse() 

    if *filename == "" { 
     log.Fatal("Provide a file to parse") 
    } 

    fmt.Println("Getting file") 

    names := make(chan string) 
    readerr := make(chan error) 

    go GetLine(*filename, names, readerr) 

loop: 
    for { 
     select { 
     case name := <-names: 
      // Process each line 
      fmt.Println(name) 

     case err := <-readerr: 
      if err != nil { 
       log.Fatal(err) 
      } 
      break loop 
     } 
    } 

    fmt.Println("Processing Complete") 
} 

func GetLine(filename string, names chan string, readerr chan error) { 
    file, err := os.Open(filename) 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer file.Close() 

    scanner := bufio.NewScanner(file) 
    for scanner.Scan() { 
     names <- scanner.Text() 
    } 
    readerr <- scanner.Err() 
} 

In diesem speziellen Beispiel kann der Code so umstrukturiert werden, dass empfangene Namen vom Empfangen des Fehlers getrennt werden.

func main() { 
    filename := flag.String("filename", "", "The file to parse") 
    flag.Parse() 

    if *filename == "" { 
     log.Fatal("Provide a file to parse") 
    } 

    fmt.Println("Getting file") 

    names := make(chan string) 
    readerr := make(chan error) 

    go GetLine(*filename, names, readerr) 

    for name := range names { 
     fmt.Println(name) 
    } 
    if err := <-readerr; err != nil { 
     log.Fatal(err) 
    } 

    fmt.Println("Processing Complete") 
} 

func GetLine(filename string, names chan string, readerr chan error) { 
    file, err := os.Open(filename) 
    if err != nil { 
     log.Fatal(err) 
    } 
    defer file.Close() 

    scanner := bufio.NewScanner(file) 
    for scanner.Scan() { 
     names <- scanner.Text() 
    } 
    close(names) // close causes range on channel to break out of loop 
    readerr <- scanner.Err() 
} 
+0

Vielen Dank! TIL: 'break' kann verwendet werden, um aus' 'select''-Anweisungen in Go auszubrechen. Gibt es hier eine Alternative zum Label? Jedes idiomatische Go-Muster? –

+0

@KshitijSaraogi Sie können vermeiden, Beschriftungen zu verwenden, wenn Sie die Anweisung 'for' in eine Funktion (die ein Funktionsliteral sein kann) einfügen und' return' anstelle von 'break loop' verwenden. – icza

+0

Die Bezeichnung ist der idiomatische Weg, aus einem 'for {select {}}' auszubrechen. Der Code kann umstrukturiert werden, um das 'for {select {}}' zu vermeiden und somit das Label zu vermeiden. Siehe aktualisierte Antwort. –