在Golang中同时读取文件

时间:2014-11-30 19:46:13

标签: go

阅读部分不是并发的,但处理是。我用这种方式表达了标题,因为我最有可能再次使用该短语来搜索这个问题。 :)

我在尝试超越示例之后遇到了僵局,所以这对我来说是一次学习经历。我的目标是:

  1. 逐行读取文件(最终使用缓冲区来处理行组)。
  2. 将文本传递给执行某些正则表达式工作的func()
  3. 将结果发送到某处但避免使用互斥锁或共享变量。我将整数(总是数字1)发送到频道。这有点愚蠢但是如果它没有引起问题我不想这样离开,除非你们有一个更整洁的选择。
  4. 使用工作池执行此操作。我不确定我是怎么告诉工人们自己重新排队的?
  5. 这是playground link。我试着写有用的评论,希望这是有道理的。我的设计可能完全错误,所以不要犹豫重构。

    package main
    
    import (
      "bufio"
      "fmt"
      "regexp"
      "strings"
      "sync"
    )
    
    func telephoneNumbersInFile(path string) int {
      file := strings.NewReader(path)
    
      var telephone = regexp.MustCompile(`\(\d+\)\s\d+-\d+`)
    
      // do I need buffered channels here?
      jobs := make(chan string)
      results := make(chan int)
    
      // I think we need a wait group, not sure.
      wg := new(sync.WaitGroup)
    
      // start up some workers that will block and wait?
      for w := 1; w <= 3; w++ {
        wg.Add(1)
        go matchTelephoneNumbers(jobs, results, wg, telephone)
      }
    
      // go over a file line by line and queue up a ton of work
      scanner := bufio.NewScanner(file)
      for scanner.Scan() {
        // Later I want to create a buffer of lines, not just line-by-line here ...
        jobs <- scanner.Text()
      }
    
      close(jobs)
      wg.Wait()
    
      // Add up the results from the results channel.
      // The rest of this isn't even working so ignore for now.
      counts := 0
      // for v := range results {
      //   counts += v
      // }
    
      return counts
    }
    
    func matchTelephoneNumbers(jobs <-chan string, results chan<- int, wg *sync.WaitGroup, telephone *regexp.Regexp) {
      // Decreasing internal counter for wait-group as soon as goroutine finishes
      defer wg.Done()
    
      // eventually I want to have a []string channel to work on a chunk of lines not just one line of text
      for j := range jobs {
        if telephone.MatchString(j) {
          results <- 1
        }
      }
    }
    
    func main() {
      // An artificial input source.  Normally this is a file passed on the command line.
      const input = "Foo\n(555) 123-3456\nBar\nBaz"
      numberOfTelephoneNumbers := telephoneNumbersInFile(input)
      fmt.Println(numberOfTelephoneNumbers)
    }
    

2 个答案:

答案 0 :(得分:13)

你几乎就在那里,只需要做一些关于goroutines&#39;同步。您的问题是您尝试提供解析器并在同一例程中收集结果,但这无法完成。

我建议如下:

  1. 在单独的例程中运行扫描程序,一旦读取所有内容,关闭输入通道。
  2. 运行单独的例程,等待解析器完成其工作,而不是关闭输出通道。
  3. 将所有结果收集到您的主程序中。
  4. 相关更改可能如下所示:

    // Go over a file line by line and queue up a ton of work
    go func() {
        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
            jobs <- scanner.Text()
        }
        close(jobs)
    }()
    
    // Collect all the results...
    // First, make sure we close the result channel when everything was processed
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // Now, add up the results from the results channel until closed
    counts := 0
    for v := range results {
        counts += v
    }
    

    在操场上完全运作的例子:http://play.golang.org/p/coja1_w-fY

    值得补充的是,您不一定需要WaitGroup来实现同样的目标,您需要知道的是何时停止接收结果。这可以通过扫描仪广告(在频道上)读取多少行然后收集器只读取指定数量的结果(尽管你也需要发送零)来实现。

答案 1 :(得分:1)

编辑:@tomasz上面的回答是正确的。请忽略这个答案。

你需要做两件事:

  1. 使用buffered chan,以便发送不阻止
  2. 关闭结果chan,以便接收不会阻止。
  3. 使用缓冲通道至关重要,因为无缓冲通道需要为每次发送接收,这会导致您遇到的死锁。

    如果你解决了这个问题,当你试图收到结果时,你会遇到僵局,因为结果还没有结束。

    这里是固定的游乐场:http://play.golang.org/p/DtS8Matgi5