读取并发goroutine中写入的通道时,最后一个值丢失。

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

Last value is missing when reading channel written in concurrent goroutines

问题

我是你的中文翻译助手,以下是你提供的代码的翻译:

我在Go语言中还比较新手,我想要异步运行多个任务,等待它们全部完成,并将它们的结果收集到一个切片中。

我阅读了很多文档和示例,特别是Nathan LeClaire的文章,并且根据自己的需求编写了类似的代码(见下方代码)。机制很简单:

  • 触发了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]     // 注意这里缺少了9
英文:

I'm fairly new in Go and I want to run several tasks asynchronously, wait for all of them to be finished and collect their results into a slice.

I was reading a lot of documentation and examples, specially this Nathan LeClaire's post, and came up with something close to what I want to do (see code below). The mechanic is simple:

  • 10 goroutines are triggered and each of them writes a value in a channel.
  • Another goroutine reads the channel and fills the slice.
  • After all these stuff are done, the slice is printed.

However the outcome shows a 9-length slice (values from 0 to 8) and the 10th value (should be 9) seems missing. The program exits just fine and I don't know what is going on. Any hint is appreciated.

Here is a code example to play with http://play.golang.org/p/HUFOZLmCto:

package main

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

func main() {

    var wg sync.WaitGroup
    
    n := 10    
    c := make(chan int)
    
    wg.Add(n)
    
    for i := 0; i &lt; n; i++ {
        go func(val int) {
            defer wg.Done()
            fmt.Println(&quot;Sending value to channel: &quot;, val)
            c &lt;- val
        }(i)
    }
    
    var array []int
    
    go func() {
        for val := range c {
            fmt.Println(&quot;Recieving from channel: &quot;, val)
            array = append(array, val)
        }
    }()
    
    wg.Wait()
    fmt.Println(&quot;Array: &quot;, array)
}

And this is the outcome:

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

答案1

得分: 5

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

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

一种安排的方法是将接收循环放在主函数中,并在其自己的goroutine中等待关闭c通道:

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

for val := range c {
	fmt.Println("从通道接收:", val)
	array = append(array, val)
}

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

英文:

You're exiting before the receiving goroutine has a chance to receive and handle the value. There is also a race condition on the array variable, where main may try to print the array during the appendoperation.

Note that even though an using unbuffered channel would create a synchronization point between the two loops, and guarantee that the receive loop has the value before wg.Done(), it wouldn't guarantee that the fmt.Println and append happen before main continues.

One way to arrange this is to put the receive loop in main, and wait on closing the c chan in it's own goroutine:

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

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

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

答案2

得分: 2

很简单。在某个时刻,所有的值都被写入,waitGroup被释放,然后一个goroutine正在填充slice。由于waitGroup被释放,可能会在通道被排空到slice之前发生打印。

为了解决这个问题,将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("发送值到通道: ", val)
			c <- val
		}(i)
	}

	var array []int

	go func() {

		for val := range c {
			fmt.Println("从通道接收: ", val)
			array = append(array, val)
			wg.Done()
		}
	}()

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

playground中有示例。

英文:

Pretty simple. At some point all the values are written, the waitGroup is released and a goroutine is filling the sice. Since the waitGroup is release its possible for the print to occur before the channel is drained into the slice.

To solve, move the wg.Done() into the reader to prevent the print happening before the draining.

package main

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

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

	var wg sync.WaitGroup
	wg.Add(10)

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

	var array []int

	go func() {

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

	wg.Wait()
	fmt.Println(&quot;Array: &quot;, array)
}

Example in playground

huangapple
  • 本文由 发表于 2016年2月19日 04:44:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/35492051.html
匿名

发表评论

匿名网友

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

确定