多个Go协程从一个通道中消费数据导致数据丢失。

huangapple go评论68阅读模式
英文:

Multiple go routines consuming from a channel causing loss of data

问题

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

代码:

var c = make(chan int)

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

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

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

输出结果为:

协程 1 : 0
协程 1 : 2
协程 2 : 1
协程 1 : 3
协程 1 : 4

这里缺少值5,并且从未打印出来!为什么会这样?如果我移除调用 go g(),它就能正常工作。

另外,如果我将通道改为有缓冲的,例如:

var c = make(chan int, 10)

就没有任何输出了。我理解无缓冲通道中的发送操作在接收操作完成之后才会完成,而对于有缓冲通道来说,如果通道尚未发送任何整数,那么for循环会被阻塞,将其视为nil通道吗?

请帮忙解答我这两个问题。非常感谢你的帮助。

英文:

I am a newbie to Go. In my example below, multiple go routines are consuming from an unbuffered channel.

Code :

var c = make(chan int)

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

func g() {
	fmt.Printf(&quot;routine 2 : %v\n&quot;, &lt;-c)
}

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

The output was :

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

Value 5 is missing from this and never gets printed ! Why is this happening? If I remove the call - go g(), it works perfectly.

Also, if I make the channel buffered, say :

var c = make(chan int, 10)

There is no output at all. I understand that for unbuffered channel, the send completes after receive completes, which is not the case for buffered. Still, for buffered case, if channel has not yet sent any int, wouldn't the for loop be blocked considering it a nil channel?

Please help out with both my queries. Appreciate all the inputs.

答案1

得分: 6

一旦5被消耗完,程序就会退出。它没有时间打印输出。如果你多次运行程序,你可能会发现在某些情况下,它确实在关闭之前打印输出,但这完全是随机的。

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

英文:

As soon as 5 is consumed, the program exits. There's no time for it to print the output. If you run the program enough times, you may find that on some occasions, it does happen to print the output before it closes, but it'll be purely random.

You need to add some mechanism to wait for your channels to finish, before exiting the program.

答案2

得分: 2

@Flimzy提到的是正确的,在读取完成后,发送操作会解除阻塞,即使在打印完成之前主go例程关闭通道并退出(有时可能会完成,所以偶尔会看到)。一旦主go例程退出,所有其他go例程也会退出。这里有一个使用WaitGroup进行同步的解决方案:https://play.golang.org/p/i2uHw3X1G3 - 使用这个解决方案,你不应该再看到任何遗漏。希望这可以帮到你。

对于带缓冲的通道,在写入值后,主go例程不需要等待读取完成,所以它只是在发送完所有值后退出。因此,你需要使用同步来解决这个问题。

英文:

What @Flimzy mentions is correct, after the read is over the send unblocks and the main go routine exits after closing the channel even before the print could complete (sometimes it may complete so you may see it once in a while). Once the main go routine exits, so do all other go routines. Here's a solution which uses WaitGroup for synchronization https://play.golang.org/p/i2uHw3X1G3 - with this you should not see any missouts. Hope this helps.

For buffered channel, the main go routine doesn't have to wait for the read to complete after writing the values, so it just exits after it sends all the values. So again, you need to use synchronization for this.

答案3

得分: 1

你需要等待goroutine完成:

方法1:使用sync.WaitGroup

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:使用退出通道(quit channel):

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
英文:

You need to wait for goroutines to finish:


method 1: using 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.

try this:

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

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

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

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

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

sample output:

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

method 2: using quit channel, try this:

package main

import &quot;fmt&quot;

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

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

output:

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

huangapple
  • 本文由 发表于 2017年8月28日 16:38:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/45914931.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定