panic:运行时错误:当同时作为goroutine运行时,切片边界超出范围

时间:2017-12-29 19:15:18

标签: go concurrency runtime-error

我将一个函数作为goroutine调用,并使用WaitGroup来防止在它们全部完成之前关闭共享扫描器。 myfunc()函数遍历文件。我想对这个文件进行内存映射,并在所有goroutine之间共享它,而不是每次都有从磁盘读取的I / O阻塞点。我被告知这种方法可行in an answer to another question.但是,虽然这个功能很好地独立工作,但它并不能同时工作。我收到错误:

panic: runtime error: slice bounds out of range

但错误是当我调用Scan()方法(不在片上)时会出现混乱。

这是一个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"
    }
}

我原本以为问题是对共享Sizes地图的并发访问,但是将其移到myfunc()内(并且每次都无效地重新定义/重新定义它)仍然会导致相同的错误,这与调用{ {1}}。我试图遵循我收到的指导in this answer.

这里是恐慌的完整堆栈痕迹:

Scan()

第81行是:

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

第113行是:

go myfunc(&param, scanner, ch)

1 个答案:

答案 0 :(得分:1)

实际上在审核了Scan来源后,它似乎不是线程安全的。您可以通过扫描一个例程来解决这个问题,并且任何数量的其他例程都会消耗线并处理它们:

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

另请注意,defer函数的最后一行没有意义; defer的重点是在函数体中的某个地方调用它,并知道它将在函数返回后被调用。由于您使用WaitGroup来阻止函数返回,直到您完成扫描仪,您可以立即安全地推迟关闭。