等待所有的例程完成

时间:2017-10-04 11:37:40

标签: go concurrency goroutine

第一次使用go,并尝试开始例程并WaitGroups正在工作。

我有一个包含100行数据的CSV文件。 (101包括标题)

我有以下简单的代码:

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "sync"
    "time"
)

func main() {
    start := time.Now()
    numRows := 0

    waitGroup := sync.WaitGroup{}
    file, _ := os.Open("./data.csv")

    scanner := bufio.NewScanner(file)
    scanner.Scan() // to read the header

    for scanner.Scan() {
        err := scanner.Err()

        if err != nil && err != io.EOF {
            panic(err)
        }

        waitGroup.Add(1)

        go (func() {
            numRows++
            waitGroup.Done()
        })()
    }

    waitGroup.Wait()
    file.Close()

    fmt.Println("Finished parsing ", numRows)
    fmt.Println("Elapsed time in seconds: ", time.Now().Sub(start))
}

当我运行时,numRows输出每次在94到100之间波动。我希望每次都是100。如果我在包含10行数据的CSV上运行相同的代码,则每次都会输出10

在我看来,最后的几个例程并没有及时完成。

我已尝试过以下失败的内容:

  • 使用CsvReader代替Scanner
  • waitGroup.Add(1)移至匿名func
  • 下方
  • 将匿名函数移出到包级别的范围函数中(并使用ptrs传递事物)

我缺少什么?

2 个答案:

答案 0 :(得分:7)

在不同的goroutine中同时修改单个变量是不安全的。您对numRows的部分更新将会丢失,有时您的程序可能会崩溃。

使用互斥锁保护您的numRows变量,或使用其中一个atomic函数以原子方式进行添加:

var numRows int32

// ...

    go (func() {
        atomic.AddInt32(&numRows, 1)
        waitGroup.Done()
    })()

答案 1 :(得分:1)

您如何处理此代码:

for scanner.Scan() {
    err := scanner.Err()

    if err != nil && err != io.EOF {
        panic(err)
    }

    waitGroup.Add(1)

    go (func() {
        numRows++
        waitGroup.Done()
    })()
}

实际上所有工作都在一个主要的goroutine中完成,只有numRows增量使用单独的goroutine。我认为这可以简化为简单的增量:

for scanner.Scan() {
    err := scanner.Err()

    if err != nil && err != io.EOF {
        panic(err)
    }
    numRows++
}

如果要模拟并行解析和流水线操作,可以使用通道。只有一个goroutine负责计数器增量。每次当另一个goroutine想要增加计数器时 - 它会向该频道发送一条消息。

https://play.golang.org/p/W60twJjY8P