即使频道被关闭,常规也会陷入僵局

met*_*eto 0 concurrency go goroutine

我有一个列表,其中包含一个弹出元素的函数,以及另一个"接收"弹出元素的函数.我认为接收器关闭后会关闭通道,但似乎该程序在到达之前已经死锁.这是最好的方法吗?我应该有另一个通道来检测弹出窗口何时完成?

游乐场链接

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

}
Run Code Online (Sandbox Code Playgroud)

One*_*One 6

代码有很多问题,让我们看看.

  1. pop访问切片时,您的函数不会锁定,因此这就是数据竞争.
  2. for len(list) > 0 {} 是一个数据竞赛,因为您在其他2个goroutine中修改它时访问列表.
  3. for len(list) > 0 {} 将永远不会返回,因为您的列表中有3个项目,但您只调用pop两次.
  4. receiver(c) 由于#3的错误,它试图从频道读取,但没有任何写入它.

一种方法是使用一个writer(pop)和多个reader(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")
}
Run Code Online (Sandbox Code Playgroud)

playground

go run -race blah.go在弄乱goroutines时总是使用.

  • +1,虽然`-race`没有检测到所有内容(http://stackoverflow.com/a/20282328/6309) (2认同)