英文:
go routines deadlocked even if channel was closed
问题
我有一个列表,其中有一个函数从中弹出元素,另一个函数“接收”弹出的元素。我以为在接收函数之后放置一个 close 操作会关闭通道,但似乎程序在那之前就陷入了死锁。哪种方法是最好的?我是否应该有另一个通道来检测弹出操作是否完成?
func pop(list *[]int, c chan int) {
if len(*list) != 0 {
result := (*list)[0]
*list = (*list)[1:]
fmt.Println("about to send ", result)
c <- result
} else {
return
}
}
func receiver(c chan int) {
result := <-c
fmt.Println("received ", result)
}
var list = []int{1, 2, 3}
func main() {
fmt.Println("Main")
c := make(chan int)
go pop(&list, c)
go pop(&list, c)
for len(list) > 0 {
receiver(c)
}
close(c) // 看起来没有任何效果
fmt.Println("done")
}
你可以尝试在 main
函数中使用 sync.WaitGroup
来等待所有的 goroutine 完成,然后再关闭通道。这样可以确保所有的弹出操作都完成了。以下是修改后的代码:
func main() {
fmt.Println("Main")
c := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
go func() {
pop(&list, c)
wg.Done()
}()
go func() {
pop(&list, c)
wg.Done()
}()
wg.Wait()
close(c)
fmt.Println("done")
}
希望对你有帮助!
英文:
I have a list, with a function that pop element from it, and another function that "receives" the popped elements. I thought that putting a close after the receiver would close the channel, but it seems that the program is deadlock before getting there. Which is the best way of doing this? Should I have another channel that detects when the pop are done?
func pop(list *[]int, c chan int) {
if len(*list) != 0 {
result := (*list)[0]
*list = (*list)[1:]
fmt.Println("about to send ", result)
c <- result
} else {
return
}
}
func receiver(c chan int) {
result := <-c
fmt.Println("received ", result)
}
var list = []int{1, 2, 3}
func main() {
fmt.Println("Main")
c := make(chan int)
go pop(&list, c)
go pop(&list, c)
for len(list) > 0 {
receiver(c)
}
close(c) //Dosen't seem to have any effect
fmt.Println("done")
}
答案1
得分: 6
代码存在很多问题,让我们来看看。
- 你的
pop
函数在访问切片时没有进行锁定,这就是一个数据竞争。 for len(list) > 0 {}
是一个数据竞争,因为你在修改它的同时还在访问列表的其他两个goroutine。for len(list) > 0 {}
永远不会返回,因为你的列表中有3个项目,但你只调用了两次pop。receiver(c)
出错是因为问题3,它尝试从通道中读取,但没有任何东西写入它。
一种解决方法是使用一个写入者(pop
)和多个读取者(receiver
):
func pop(list *[]int, c chan int, done chan bool) {
for len(*list) != 0 {
result := (*list)[0]
*list = (*list)[1:]
fmt.Println("about to send ", result)
c <- result
}
close(c)
done <- true
}
func receiver(c chan int) {
for result := range c {
fmt.Println("received ", result)
}
}
var list = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
func main() {
c := make(chan int)
done := make(chan bool)
go pop(&list, c, done)
go receiver(c)
go receiver(c)
go receiver(c)
<-done
fmt.Println("done")
}
在处理goroutine时,始终使用go run -race blah.go
。
英文:
There are so many problems with the code, let's see.
- your
pop
function doesn't lock when accessing the slice, so that's a data race right there. for len(list) > 0 {}
is a data race because you're accessing list while modifying it in 2 other goroutines.for len(list) > 0 {}
will never return because you have 3 items in your list but you call pop only twice.receiver(c)
errors because of #3, it tries to read from the channel but there's nothing writing to it.
One way to do it is to use one writer (pop
) and multiple readers (receiver
):
func pop(list *[]int, c chan int, done chan bool) {
for len(*list) != 0 {
result := (*list)[0]
*list = (*list)[1:]
fmt.Println("about to send ", result)
c <- result
}
close(c)
done <- true
}
func receiver(c chan int) {
for result := range c {
fmt.Println("received ", result)
}
}
var list = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
func main() {
c := make(chan int)
done := make(chan bool)
go pop(&list, c, done)
go receiver(c)
go receiver(c)
go receiver(c)
<-done
fmt.Println("done")
}
Always use go run -race blah.go
when messing with goroutines.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论