2017-12-29 12 views
0

Ich rufe eine Funktion als Goroutine und mit einer WaitGroup, um zu verhindern, einen freigegebenen Scanner zu schließen, bevor sie alle fertig sind. Die myfunc() Funktion iteriert über eine Datei. Ich wollte diese Datei im Speicher abbilden und sie zwischen allen Gououtines teilen, anstatt jedesmal den I/O-Chokepunkt des Lesens von der Platte zu haben. Mir wurde gesagt, dass dieser Ansatz funktionieren würde Während diese Funktion jedoch einwandfrei funktionierte, funktioniert es nicht gleichzeitig. Ich erhalte den Fehler:panic: Laufzeitfehler: Slice-Grenzen außerhalb des Bereichs, wenn gleichzeitig als Goroutine ausgeführt

panic: runtime error: slice bounds out of range 

aber der Fehler ist, wenn ich die Scan() Methode aufrufen (nicht auf einer Scheibe), die verwirrend ist. Hier

ist ein MWE:

// ... package declaration; imports; yada yada 

// the actual Sizes map is much more meaningful, this is just for the MWE 
var Sizes = map[int]string { 
    10: "Ten", 
    20: "Twenty", 
    30: "Thirty", 
    40: "Forty", 
} 

type FileScanner struct { 
    io.Closer 
    *bufio.Scanner 
} 

func main() { 
    // ... validate path to file stored in filePath variable 
    filePath := "/path/to/file.txt" 

    // get word list scanner to be shared between goroutines 
    scanner := getScannerPtr(&filePath) 

    // call myfunc() for each param passed 
    var wg sync.WaitGroup 
    ch := make(chan string) 
    for _, param := range os.Args[1:] { 
     wg.Add(1) 
     go myfunc(&param, scanner, ch) 
     wg.Done() 
    } 

    // print results received from channel 
    for range os.Args[1:] { 
     fmt.Println(<-ch) // print data received from channel ch 
    } 

    // don't close scanner until all goroutines are finished 
    wg.Wait() 
    defer scanner.Close() 
} 

func getScannerPtr(filePath *string) *FileScanner { 
    f, err := os.Open(*filePath) 
    if err != nil { 
     fmt.Fprint(os.Stderr, "Error opening file\n") 
     panic(err) 
    } 
    scanner := bufio.NewScanner(f) 
    return &FileScanner{f, scanner} 
} 

func myfunc(param *string, scanner *FileScanner, ch chan<-string) { 
    for scanner.Scan() { 
     line := strings.TrimSpace(scanner.Text()) 
     // ... do something with line (read only) 
     // ... access shared Sizes map when doing it (read only) 
     ch <- "some string result goes here" 
    } 
} 

Ich dachte ursprünglich das Problem gleichzeitigen Zugriff auf den gemeinsam genutzten Größen Karte war, aber es innerhalb myfunc() (und ineffizienten erneut deklariert/Neudefinition es jedes Mal) noch in Folge der gleichen bewegten Fehler, der mit dem Aufruf von Scan() zu tun hat. Ich bin versucht, die Führung ich in this answer.

Hier ist der vollständige Stack-Trace der Panik empfangen zu folgen:

panic: runtime error: slice bounds out of range 

goroutine 6 [running]: 
bufio.(*Scanner).Scan(0xc42008a000, 0x80) 
     /usr/local/go/src/bufio/scan.go:139 +0xb3e 
main.crack(0xc42004c280, 0xc42000a080, 0xc42001c0c0) 
     /Users/dan/go/src/crypto_ctf_challenge/main.go:113 +0x288 
created by main.main 
     /Users/dan/go/src/crypto_ctf_challenge/main.go:81 +0x1d8 
exit status 2 

Zeile 81 ist:

go myfunc(&param, scanner, ch) 

Linie 113:

for scanner.Scan() { 

Antwort

1

Eigentlich nach Überprüfung der Scan Quelle, es erscheint nicht ar threadsicher sein. Sie können aus der Scanner lesen eine Routine dieses Problem umgehen, indem er, und eine beliebige Anzahl von anderen Routinen verbrauchen Linien und verarbeiten sie:

func main() { 
    // ... validate path to file stored in filePath variable 
    filePath := "/path/to/file.txt" 

    // get word list scanner to be shared between goroutines 
    scanner := getScannerPtr(&filePath) 
    defer scanner.Close() 

    // call myfunc() for each param passed 
    var wg sync.WaitGroup 
    ch := make(chan string) 
    lines := make(chan string) 
    go func() { 
     for scanner.Scan() { 
      lines <- scanner.Text() 
     } 
     close(lines) 
    }() 
    for _, param := range os.Args[1:] { 
     wg.Add(1) 
     go myfunc(param, lines, ch) 
     wg.Done() 
    } 

    // print results received from channel 
    for range os.Args[1:] { 
     fmt.Println(<-ch) // print data received from channel ch 
    } 

    // don't close scanner until all goroutines are finished 
    wg.Wait() 
} 

func myfunc(param string, lines chan []byte, ch chan<-string) { 
    for line := range lines { 
     line = strings.TrimSpace(line) 
     // ... do something with line (read only) 
     // ... access shared Sizes map when doing it (read only) 
     ch <- "some string result goes here" 
    } 
} 

Beachten Sie auch, dass es in defer ing die letzte Zeile in einer Funktion keinen Sinn; Der ganze Sinn von defer ist, es irgendwo im Körper der Funktion zu nennen und zu wissen, dass es aufgerufen wird, nachdem die Funktion zurückkehrt. Da Sie eine WaitGroup verwenden, um zu verhindern, dass die Funktion zurückkehrt, bis Sie mit Ihrem Scanner fertig sind, können Sie den Schließvorgang sofort aufschieben.

+0

Dies ist in der Tat der richtige Weg, um Scan zu verwenden, aber ich würde vorschlagen, die Go-Routinen zu erstellen, bevor Sie mit dem Lesen beginnen, andernfalls können Sie den Kanal füllen und einen Deadlock erstellen, da nichts es ablässt. Auch 'wg.Done()' sollte nicht in main sein. – Verran

+0

Wird es irgendwelche Probleme geben, wenn 'myfunc' einen Byte-Slice-Kanal nimmt, während der Linien-Kanal deklariert wurde, um Strings zu nehmen? – Dan

+0

@veran es wird nicht Deadlock, weil der Scanner in einer separaten Goroutine ist. Es wird sitzen und warten, bis die Verbraucher kommen. @Dan, Byte Slices und Strings sind leicht konvertierbar, aber es verursacht ein Alloc. Ich bin mit String gegangen, weil 'Scanner.Text()' das zurückgibt. – Adrian

Verwandte Themen