【问题标题】:Last value is missing when reading channel written in concurrent goroutines读取并发 goroutine 中写入的通道时缺少最后一个值
【发布时间】:2016-05-31 05:51:08
【问题描述】:

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

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

  • 10 个 goroutine 被触发,每个 goroutine 都在通道中写入一个值。
  • 另一个 goroutine 读取通道并填充切片。
  • 所有这些都完成后,切片就打印出来了。

但是,结果显示了一个 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

【问题讨论】:

    标签: go concurrency channel


    【解决方案1】:

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

    请注意,即使使用无缓冲通道会在两个循环之间创建一个同步点,并保证接收循环具有wg.Done() 之前的值,它也不能保证fmt.Println 和append 会发生在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

    【讨论】:

    • 哇,这出奇的快,太棒了!感谢您的解决方案,但主要是为了解释。
    【解决方案2】:

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

    要解决这个问题,请将 wg.Done() 移到阅读器中,以防止在排空之前发生打印。

    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 中的示例

    【讨论】:

    • 感谢这个解决方案,它也适用于 1+。但是,我们不应该在发送方例程而不是接收方调用wg.Done() 吗?
    • 不是真的,等待组一直阻塞,直到所有值都被读入数组。 wg 在写入器、读取器和打印机之间共享,而读取器可以知道数组何时准备好。我真的很喜欢 jimB 的回答,因为它正确地关闭了通道,这是一种更高级的方式来表示作者已经完成。它暗示了将读者提升到主要的重构,但实际上是最好的信号方式。在这里查看 gobyexample.com/closing-channels
    猜你喜欢
    • 1970-01-01
    • 2015-06-03
    • 2019-10-08
    • 2021-09-13
    • 1970-01-01
    • 2018-03-03
    • 2022-01-23
    • 2023-02-07
    • 2012-10-06
    相关资源
    最近更新 更多