读取以并发 goroutine 编写的通道时缺少最后一个值

Last value is missing when reading channel written in concurrent goroutines

我是 Go 的新手,我想 运行 异步执行多个任务,等待所有任务完成并将它们的结果收集到一个切片中。

我阅读了很多文档和示例,特别是这个 Nathan LeClaire's post,并想出了一些接近我想做的事情(见下面的代码)。机制很简单:

但是结果显示长度为 9 的切片(值从 0 到 8)并且第 10 个值(应该是 9)似乎丢失了。该程序退出得很好,我不知道发生了什么。任何提示表示赞赏。

这里有一个代码示例可以玩 http://play.golang.org/p/HUFOZLmCto:

package main

import (
    "fmt"
    "sync"
)

func main() {

    var wg sync.WaitGroup

    n := 10    
    c := make(chan int)

    wg.Add(n)

    for i := 0; i < n; i++ {
        go func(val int) {
            defer wg.Done()
            fmt.Println("Sending value to channel: ", val)
            c <- val
        }(i)
    }

    var array []int

    go func() {
        for val := range c {
            fmt.Println("Recieving from channel: ", val)
            array = append(array, val)
        }
    }()

    wg.Wait()
    fmt.Println("Array: ", array)
}

这是结果:

Sending value to channel:  0
Recieving from channel:  0
Sending value to channel:  1
Recieving from channel:  1
Sending value to channel:  2
Recieving from channel:  2
Sending value to channel:  3
Recieving from channel:  3
Sending value to channel:  4
Recieving from channel:  4
Sending value to channel:  5
Recieving from channel:  5
Sending value to channel:  6
Recieving from channel:  6
Sending value to channel:  7
Recieving from channel:  7
Sending value to channel:  8
Recieving from channel:  8
Sending value to channel:  9
Array:  [0 1 2 3 4 5 6 7 8]     // Note 9 is missing here

您在接收 goroutine 有机会接收和处理该值之前退出。 array 变量也存在竞争条件,其中 main 可能会在 append 操作期间尝试打印数组。

请注意,即使使用无缓冲通道会在两个循环之间创建一个同步点,并保证接收循环具有 wg.Done() 之前的值,但不能保证 fmt.Printlnappend 发生在 main 继续之前。

安排这个的一种方法是将接收循环放在 main 中,并在它自己的 goroutine 中等待关闭 c chan

go func() {
    wg.Wait()
    close(c)
}()

for val := range c {
    fmt.Println("Recieving from channel: ", val)
    array = append(array, val)
}

http://play.golang.org/p/YReTVZtsUv

很简单。在某个时候所有的值都被写入,waitGroup 被释放并且一个 goroutine 正在填充 sice。由于 waitGroup 已释放,因此有可能在通道被排入切片之前进行打印。

要解决,将 wg.Done() 移动到 reader 以防止在排水之前发生打印。

package main

import (
    "fmt"
    "sync"
)

func main() {
    n := 10
    c := make(chan int)

    var wg sync.WaitGroup
    wg.Add(10)

    for i := 0; i < n; i++ {
        go func(val int) {
            fmt.Println("Sending value to channel: ", val)
            c <- val
        }(i)
    }

    var array []int

    go func() {

        for val := range c {
            fmt.Println("Recieving from channel: ", val)
            array = append(array, val)
            wg.Done()
        }
    }()

    wg.Wait()
    fmt.Println("Array: ", array)
}

playground

中的例子