使用goroutines处理值并将结果收集到切片中

时间:2017-09-02 05:41:42

标签: go slice goroutine

我最近在探索Go以及goroutines的工作方式让我很困惑。

我尝试使用goroutines将之前编写的代码移植到Go中,但出现fatal error: all goroutines are asleep - deadlock!错误。

我尝试做的是使用goroutines处理列表中的项目,然后将处理后的值收集到新列表中。但我在收集"中遇到了问题。一部分。

代码:

sampleChan := make(chan sample)
var wg sync.WaitGroup

// Read from contents list
for i, line := range contents {
    wg.Add(1)
    // Process each item with a goroutine and send output to sampleChan
    go newSample(line, *replicatePtr, *timePtr, sampleChan, &wg)
}
wg.Wait()

// Read from sampleChan and put into a slice
var sampleList []sample
for s := range sampleChan {
    sampleList = append(sampleList, s)
}
close(sampleChan)

从goroutines收集结果的正确方法是什么?

我知道切片不是线程安全的,所以我不能让每个goroutine都附加到切片上。

2 个答案:

答案 0 :(得分:5)

您的代码几乎是正确的。有几个问题:首先,你在收集结果之前等待所有工人完成,第二个你的for循环在通道关闭时终止,但通道仅在{{1循环终止。

您可以通过在工作人员完成时异步关闭频道来修复代码:

for

作为样式的注释(以及https://github.com/golang/go/wiki/CodeReviewComments#synchronous-functions之后的注释),如果for i, line := range contents { wg.Add(1) // Process each item with a goroutine and send output to sampleChan go newSample(line, *replicatePtr, *timePtr, sampleChan, &wg) } go func() { wg.Wait() close(sampleChan) }() for s := range sampleChan { .. } 是一个简单的同步函数,它不会占用等待组和通道,而只是生成其结果。然后工人代码看起来像:

newSample

这使您的并发原语保持在一起,除了简化for i, line := range contents { wg.Add(1) go func(line string) { defer wg.Done() sampleChan <- newSample(line, *replicatePtr, *timePtr) }(line) } 并使其更容易测试之外,它还允许您查看并发性发生的情况,并直观地检查newSample是否始终如一调用。如果你想重构代码,例如使用固定数量的工人,那么你的更改都将是本地的。

答案 1 :(得分:2)

有两个问题

  1. 使用无缓冲通道:无缓冲通道会阻止接收器,直到通道和发送器上的数据可用,直到接收器可用。导致错误
  2. 未在范围之前关闭频道:由于您从未关闭ch频道,因此范围循环将永远不会完成。
  3. 您必须在范围

    之前使用buffered频道和close频道

    <强>代码

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func double(line int, ch chan int, wg *sync.WaitGroup) {
        defer wg.Done()
        ch <- line * 2
    
    }
    
    func main() {
        contents := []int{1, 2, 3, 4, 5}
        sampleChan := make(chan int,len(contents))
        var wg sync.WaitGroup
        // Read from contents list
        for _, line := range contents {
            wg.Add(1)
            go double(line, sampleChan, &wg)
        }
        wg.Wait()
        close(sampleChan)
        // Read from sampleChan and put into a slice
        var sampleList []int
    
        for s := range sampleChan {
            sampleList = append(sampleList, s)
        }
    
        fmt.Println(sampleList)
    }
    

    播放链接:https://play.golang.com/p/k03vt3hd3P

    修改: 另一种提高性能的方法是同时运行producerconsumer

    修改后的代码

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func doubleLines(lines []int, wg *sync.WaitGroup, sampleChan chan int) {
        defer wg.Done()
    
        defer close(sampleChan)
        var w sync.WaitGroup
        for _, line := range lines {
            w.Add(1)
            go double(&w, line, sampleChan)
        }
        w.Wait()
    }
    
    func double(wg *sync.WaitGroup, line int, ch chan int) {
        defer wg.Done()
        ch <- line * 2
    }
    
    func collectResult(wg *sync.WaitGroup, channel chan int, sampleList *[]int) {
        defer wg.Done()
        for s := range channel {
            *sampleList = append(*sampleList, s)
        }
    
    }
    
    func main() {
        contents := []int{0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}
        sampleChan := make(chan int, 1)
        var sampleList []int
    
        var wg sync.WaitGroup
    
        wg.Add(1)
        go doubleLines(contents, &wg, sampleChan)
        wg.Add(1)
        go collectResult(&wg, sampleChan, &sampleList)
        wg.Wait()
        fmt.Println(sampleList)
    }
    

    播放链接:https://play.golang.com/p/VAe7Qll3iVM