【问题标题】:Reading a file line-by-line with concurrency以并发方式逐行读取文件
【发布时间】:2018-06-10 03:07:22
【问题描述】:

我想做的事

GetLine 中,我正在尝试使用bufio.Scanner 逐行解析文件,并尝试天真地尝试并发。 在获取每一行中的文本之后,我通过string 的通道将其发送给调用者(main 函数)。除了值,我还发送错误和完成标志(通过done 频道)。因此,这应该能够在处理当前行时获取新行以在单独的 goroutine 中处理。

我实际做了什么

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
}

我的跑步收获

运行时错误:fatal error: all goroutines are asleep - deadlock!

我试图解决什么问题?

阅读this 对错误消息的回答后,我尝试在select 语句的最后一个子句中关闭通道namesreaderr,如cmets 所示。但是,该程序仍然崩溃并显示一条日志消息。我无法进一步修复它,希望能提供任何帮助。
欢迎学习资源。

P.S:我对 GoLang 比较陌生,并且仍在学习如何在 Go 中使用 CSP 并发模型。事实上,这是我第一次尝试编写同步并发程序。

【问题讨论】:

  • 死锁错误的输出应该告诉你每个 goroutine 卡在哪一行,这应该可以帮助你弄清楚为什么会发生这种情况。
  • @Adrian 感谢您的回复。到目前为止,我已经发现这个问题与 GetLine goroutine 的完整执行有关,而 select 语句仍然希望收到一个值。因此,会引发此错误。我说的对吗?

标签: go file-io goroutine


【解决方案1】:

select 中的 break 语句从 select 中跳出。完成后,应用程序必须跳出 for 循环。使用标签跳出 for 循环:

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

可以通过去掉done通道来简化代码。

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()
}

在这个特定的例子中,代码可以被重新构造,以将接收名称与接收错误分开。

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()
}

【讨论】:

  • 非常感谢! TIL:break 可用于跳出 Go 中的 select 语句。这里有没有使用标签的替代方法?任何惯用的 Go 模式?
  • @KshitijSaraogi 如果将for 语句放入函数(可能是函数字面量)中并使用return 而不是break loop,则可以避免使用标签。跨度>
  • 标签是打破for { select { } }的惯用方式。可以重组代码以避免for { select { } },从而避免使用标签。查看更新的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-30
  • 2013-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多