英文:
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 
popfunction 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论