英文:
Data loss when sending to multiple slices using channel
问题
我最近在学习使用Go语言进行并发编程,我正在考虑编写一个程序,生成一系列的数字并将它们并发地发送到三个切片中。以下是代码:
func main() {
ch := make(chan int)
done := make(chan bool)
var bag1 []int
var bag2 []int
var bag3 []int
go func() {
for i := 0; i < 1000000; i++ {
ch <- i
}
close(ch)
done <- true
}()
go sendToBag(&bag1, ch)
go sendToBag(&bag2, ch)
go sendToBag(&bag3, ch)
<-done
len1 := len(bag1)
len2 := len(bag2)
len3 := len(bag3)
fmt.Println("bag1的长度:", len1)
fmt.Println("bag2的长度:", len2)
fmt.Println("bag3的长度:", len3)
fmt.Println("总长度:", len1+len2+len3)
}
func sendToBag(bag *[]int, ch <-chan int) {
for n := range ch {
*bag = append(*bag, n)
}
}
运行结果如下:
bag1的长度:327643
bag2的长度:335630
bag3的长度:336725
总长度:999998
三个切片的总长度并不总是等于发送给它们的数字的数量。所以我的问题是:是什么导致了这个问题,如何改进代码?
英文:
I'm recently learning concurrency in golang, and I'm thinking a program with generate a series of number and then send them to three slices, concurrently. Here's the code:
func main() {
ch := make(chan int)
done := make(chan bool)
var bag1 []int
var bag2 []int
var bag3 []int
go func() {
for i := 0; i < 1000000; i++ {
ch <- i
}
close(ch)
done <- true
}()
go sendToBag(&bag1, ch)
go sendToBag(&bag2, ch)
go sendToBag(&bag3, ch)
<-done
len1 := (len(bag1))
len2 := (len(bag2))
len3 := (len(bag3))
fmt.Println("length of bag1:", len1)
fmt.Println("length of bag2:", len2)
fmt.Println("length of bag3:", len3)
fmt.Println("total length:", len1+len2+len3)
}
func sendToBag(bag *[]int, ch <-chan int) {
for n := range ch {
*bag = append(*bag, n)
}
}
which gives the output like the following:
length of bag1: 327643
length of bag2: 335630
length of bag3: 336725
total length: 999998
The total length of three slices doesn't always add up to the count of numbers which were send to them. So my question is: what caused the promblem and how to improve the code
答案1
得分: 5
你对done
通道的使用并不能保证所有3个sendToBag
goroutine完全完成它们的工作。
虽然在执行<-done
之前,for n := range ch {
语句确实会将通道“完全排空”,但是你的代码中没有任何保证<-done
在最后的*bag = append(*bag, n)
之后执行,因此不能保证len()
调用仅在append()
调用之后执行。
使用等待组(wait group)代替done
通道。
func main() {
ch := make(chan int)
wg := sync.WaitGroup{}
var bag1 []int
var bag2 []int
var bag3 []int
go func() {
for i := 0; i < 1000000; i++ {
ch <- i
}
close(ch)
}()
wg.Add(3)
go sendToBag(&bag1, ch, &wg)
go sendToBag(&bag2, ch, &wg)
go sendToBag(&bag3, ch, &wg)
wg.Wait()
len1 := len(bag1)
len2 := len(bag2)
len3 := len(bag3)
fmt.Println("bag1的长度:", len1)
fmt.Println("bag2的长度:", len2)
fmt.Println("bag3的长度:", len3)
fmt.Println("总长度:", len1+len2+len3)
}
func sendToBag(bag *[]int, ch <-chan int, wg *sync.WaitGroup) {
for n := range ch {
*bag = append(*bag, n)
}
wg.Done()
}
链接:https://go.dev/play/p/_wDgS5aS7bI
英文:
Your use of the done
channel is simply not enough to guarantee that all 3 sendToBag
goroutines finish their job completely.
While it's true that the channel is "fully drained" by the for n := range ch {
statement before <-done
is executed, there is nothing in your code that ensures that <-done
is executed after the final *bag = append(*bag, n)
is executed, and therefore there is no guarantee that the len()
calls on the bags will be executed only after the append()
calls.
Use a wait group instead of the done channel.
func main() {
ch := make(chan int)
wg := sync.WaitGroup{}
var bag1 []int
var bag2 []int
var bag3 []int
go func() {
for i := 0; i < 1000000; i++ {
ch <- i
}
close(ch)
}()
wg.Add(3)
go sendToBag(&bag1, ch, &wg)
go sendToBag(&bag2, ch, &wg)
go sendToBag(&bag3, ch, &wg)
wg.Wait()
len1 := (len(bag1))
len2 := (len(bag2))
len3 := (len(bag3))
fmt.Println("length of bag1:", len1)
fmt.Println("length of bag2:", len2)
fmt.Println("length of bag3:", len3)
fmt.Println("total length:", len1+len2+len3)
}
func sendToBag(bag *[]int, ch <-chan int, wg *sync.WaitGroup) {
for n := range ch {
*bag = append(*bag, n)
}
wg.Done()
}
答案2
得分: 0
@mkopriva的答案是首选的,但如果你只想使用通道,这里是解决方案。
func main() {
ch := make(chan int)
done := make(chan bool)
var bag1 []int
var bag2 []int
var bag3 []int
go func() {
for i := 0; i < 1000000; i++ {
ch <- i
}
close(ch)
done <- true
}()
ch1 := make(chan bool)
ch2 := make(chan bool)
ch3 := make(chan bool)
go sendToBag(&bag1, ch, ch1)
go sendToBag(&bag2, ch, ch2)
go sendToBag(&bag3, ch, ch3)
<-ch1
<-ch2
<-ch3
<-done
len1 := len(bag1)
len2 := len(bag2)
len3 := len(bag3)
fmt.Println("bag1的长度:", len1)
fmt.Println("bag2的长度:", len2)
fmt.Println("bag3的长度:", len3)
fmt.Println("总长度:", len1+len2+len3)
}
func sendToBag(bag *[]int, ch <-chan int, ch1 chan bool) {
for n := range ch {
*bag = append(*bag, n)
}
ch1 <- true
}
在sendToBag
函数中添加了更多的通道,因此主goroutine将被阻塞,直到其他goroutine在ch1/ch2/ch3上写入数据,这样可以确保在执行最后的*bag = append(*bag, n)
之后执行<-done
。
英文:
@mkopriva's answer is preferred but if you want to do with channel only, here is the solution.
func main() {
ch := make(chan int)
done := make(chan bool)
var bag1 []int
var bag2 []int
var bag3 []int
go func() {
for i := 0; i < 1000000; i++ {
ch <- i
}
close(ch)
done <- true
}()
ch1 := make(chan bool)
ch2 := make(chan bool)
ch3 := make(chan bool)
go sendToBag(&bag1, ch, ch1)
go sendToBag(&bag2, ch, ch2)
go sendToBag(&bag3, ch, ch3)
<-ch1
<-ch2
<-ch3
<-done
len1 := (len(bag1))
len2 := (len(bag2))
len3 := (len(bag3))
fmt.Println("length of bag1:", len1)
fmt.Println("length of bag2:", len2)
fmt.Println("length of bag3:", len3)
fmt.Println("total length:", len1+len2+len3)
}
func sendToBag(bag *[]int, ch <-chan int, ch1 chan bool) {
for n := range ch {
*bag = append(*bag, n)
}
ch1 <- true
}
https://go.dev/play/p/id3l_VtUq8E
Here we add more channels in the sendToBag
function, so the main goroutine will be blocked until some other goroutine write on ch1/ch2/ch3, which ensures <-done is executed after the final *bag = append(*bag, n) is executed.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论