go routines死锁,即使通道已关闭。

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

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")

}

Playground 链接

你可以尝试在 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?

Playground link

func pop(list *[]int, c chan int) {
	if len(*list) != 0 {
		result := (*list)[0]
		*list = (*list)[1:]
		fmt.Println(&quot;about to send &quot;, result)
		c &lt;- result
	} else {
		return
	}
}

func receiver(c chan int) {

	result := &lt;-c
	fmt.Println(&quot;received &quot;, result)
}

var list = []int{1, 2, 3}

func main() {

	fmt.Println(&quot;Main&quot;)
	c := make(chan int)
	go pop(&amp;list, c)
	go pop(&amp;list, c)
	for len(list) &gt; 0 {
		receiver(c)
	}
	close(c) //Dosen&#39;t seem to have any effect
	fmt.Println(&quot;done&quot;)

}

答案1

得分: 6

代码存在很多问题,让我们来看看。

  1. 你的pop函数在访问切片时没有进行锁定,这就是一个数据竞争。
  2. for len(list) > 0 {}是一个数据竞争,因为你在修改它的同时还在访问列表的其他两个goroutine。
  3. for len(list) > 0 {}永远不会返回,因为你的列表中有3个项目,但你只调用了两次pop。
  4. 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.

  1. your pop function doesn't lock when accessing the slice, so that's a data race right there.
  2. for len(list) &gt; 0 {} is a data race because you're accessing list while modifying it in 2 other goroutines.
  3. for len(list) &gt; 0 {} will never return because you have 3 items in your list but you call pop only twice.
  4. 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(&quot;about to send &quot;, result)
		c &lt;- result
	}
	close(c)
	done &lt;- true
}

func receiver(c chan int) {
	for result := range c {
		fmt.Println(&quot;received &quot;, 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(&amp;list, c, done)
	go receiver(c)
	go receiver(c)
	go receiver(c)
	&lt;-done
	fmt.Println(&quot;done&quot;)
}

<kbd>playground</kbd>

Always use go run -race blah.go when messing with goroutines.

huangapple
  • 本文由 发表于 2014年9月19日 20:49:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/25934335.html
匿名

发表评论

匿名网友

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

确定