从渠道消耗的多个例程会导致数据丢失

时间:2017-08-28 08:38:22

标签: go goroutine

我是Go的新手。在下面的示例中,多个go例程正在从无缓冲的通道中消耗。

代码:

var c = make(chan int)

func f() {
    for val := range c {    
        fmt.Printf("routine 1 : %v\n", val)
    }
}   

func g() {
    fmt.Printf("routine 2 : %v\n", <-c)
}

func main() {
    go f()
    go g()
    c <- 0
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    c <- 5
    close(c) 
}

输出结果为:

routine 1 : 0
routine 1 : 2
routine 2 : 1
routine 1 : 3
routine 1 : 4

此时缺少值5,永远不会打印!为什么会这样?如果我删除了通话 - go g(),则效果非常好。

另外,如果我将频道设为缓冲,请说:

var c = make(chan int, 10)

根本没有输出。据我所知,对于无缓冲通道,发送在接收完成后完成,而不是缓冲的情况。不过,对于缓冲情况,如果channel尚未发送任何int,那么考虑到nil通道,for循环是否会被阻塞?

请帮助解决我的疑问。感谢所有输入。

3 个答案:

答案 0 :(得分:6)

只要消耗5,程序就会退出。它没有时间打印输出。如果你运行程序足够多次,你可能会发现在某些情况下,确实会在输出关闭之前打印输出,但它会纯粹是随机的。

在退出程序之前,您需要添加一些机制来等待频道完成。

答案 1 :(得分:2)

@Flimzy提到的内容是正确的,在读取结束后发送解除阻塞并且主要的例程在关闭频道之后甚至在打印完成之前退出(有时它可能会完成,所以你可能偶尔会看到它)。一旦主要的例程退出,所有其他的例程也会退出。这是一个使用WaitGroup进行同步https://play.golang.org/p/i2uHw3X1G3的解决方案 - 您不应该看到任何错过。希望这会有所帮助。

对于缓冲通道,主go例程不必在写入值后等待读取完成,因此它在发送所有值后退出。所以,你需要为此同时使用同步。

答案 2 :(得分:1)

你需要等待goroutines完成

方法1:使用sync.WaitGroup

// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.

尝试this

package main

import (
    "fmt"
    "sync"
)

var c = make(chan int)
var wg sync.WaitGroup

func f() {
    defer wg.Done()
    for val := range c {
        fmt.Printf("routine 1 : %v\n", val)
    }
}

func g() {
    defer wg.Done()
    if data, ok := <-c; ok {
        fmt.Println("routine 2 :", data)
    }
}

func main() {
    wg.Add(2)
    defer wg.Wait()
    go f()
    go g()
    c <- 0
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    c <- 5
    close(c)
}

示例输出:

routine 2 : 0
routine 1 : 1
routine 1 : 2
routine 1 : 3
routine 1 : 4
routine 1 : 5

方法2:使用退出渠道,尝试this

package main

import "fmt"

func main() {
    go f()
    go g()
    c <- 0
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    c <- 5
    close(c)
    <-quit
    <-quit
}
func f() {
    defer done()
    for val := range c {
        fmt.Printf("routine 1 : %v\n", val)
    }
}
func g() {
    defer done()
    if data, ok := <-c; ok {
        fmt.Println("routine 2 :", data)
    }
}
func done() {
    quit <- struct{}{}
}

var c = make(chan int)
var quit = make(chan struct{}, 2)

输出:

routine 2 : 0
routine 1 : 1
routine 1 : 2
routine 1 : 3
routine 1 : 4
routine 1 : 5